diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d78d72a6a..aa524f6ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,6 @@ jobs: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu" }, { os: "macOS-13", python-architecture: "x64", rust-target: "x86_64-apple-darwin" }, { os: "windows-latest", python-architecture: "x64", rust-target: "x86_64-pc-windows-msvc" }, - { os: "windows-latest", python-architecture: "x86", rust-target: "i686-pc-windows-msvc" }, ] include: # Older versions of CPython are not available for AArch64. @@ -70,7 +69,7 @@ jobs: shell: python - name: Test run: | - pip install "numpy<2" ml_dtypes + pip install "numpy" ml_dtypes cargo test --all-features # Not on PyPy, because no embedding API if: ${{ !startsWith(matrix.python-version, 'pypy') }} @@ -83,6 +82,52 @@ jobs: CARGO_BUILD_TARGET: ${{ matrix.platform.rust-target }} RUST_BACKTRACE: 1 + test-numpy1: + name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} numpy1 + runs-on: ${{ matrix.platform.os }} + needs: [lint, check-msrv, examples] + strategy: + fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }} + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + platform: [ + { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu" }, + { os: "macOS-13", python-architecture: "x64", rust-target: "x86_64-apple-darwin" }, + { os: "windows-latest", python-architecture: "x64", rust-target: "x86_64-pc-windows-msvc" }, + ] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + architecture: ${{ matrix.platform.python-architecture }} + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.platform.rust-target }} + - name: Install toml + run: pip install toml + - name: Edit Cargo.toml and enable new resolver + run: | + import toml + cargo_toml = toml.load("Cargo.toml") + cargo_toml["package"]["resolver"] = "2" + with open("Cargo.toml", "w") as f: + toml.dump(cargo_toml, f) + shell: python + - name: Test + run: | + pip install "numpy<2" ml_dtypes + cargo test --all-features + - name: Test example + run: | + pip install nox + nox -f examples/simple/noxfile.py + env: + CARGO_TERM_VERBOSE: true + RUST_BACKTRACE: 1 + cross-build: runs-on: ubuntu-latest needs: [lint, check-msrv, examples] @@ -104,7 +149,7 @@ jobs: continue-on-error: true - uses: taiki-e/install-action@valgrind - run: | - pip install "numpy<2" ml_dtypes + pip install "numpy" ml_dtypes cargo test --all-features --release env: CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: valgrind --leak-check=no --error-exitcode=1 @@ -119,7 +164,7 @@ jobs: continue-on-error: true - uses: taiki-e/install-action@cargo-careful - run: | - pip install "numpy<2" ml_dtypes + pip install "numpy" ml_dtypes cargo careful test --all-features check-msrv: @@ -195,7 +240,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install numpy - run: pip install "numpy<2" ml_dtypes + run: pip install "numpy" ml_dtypes - uses: Swatinem/rust-cache@v2 continue-on-error: true - uses: dtolnay/rust-toolchain@stable diff --git a/CHANGELOG.md b/CHANGELOG.md index 94ecd9080..05d9d2057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Unreleased - Add `permute` and `transpose` methods for changing the order of axes of a `PyArray`. ([#428](https://github.com/PyO3/rust-numpy/pull/428)) + - Add support for NumPy v2 which had a number of changes to the [C API](https://numpy.org/devdocs/numpy_2_0_migration_guide.html#c-api-changes). ([#442](https://github.com/PyO3/rust-numpy/pull/442)) - v0.21.0 - Migrate to the new `Bound` API introduced by PyO3 0.21. ([#410](https://github.com/PyO3/rust-numpy/pull/410)) ([#411](https://github.com/PyO3/rust-numpy/pull/411)) ([#412](https://github.com/PyO3/rust-numpy/pull/412)) ([#415](https://github.com/PyO3/rust-numpy/pull/415)) ([#416](https://github.com/PyO3/rust-numpy/pull/416)) ([#418](https://github.com/PyO3/rust-numpy/pull/418)) ([#419](https://github.com/PyO3/rust-numpy/pull/419)) ([#420](https://github.com/PyO3/rust-numpy/pull/420)) ([#421](https://github.com/PyO3/rust-numpy/pull/421)) ([#422](https://github.com/PyO3/rust-numpy/pull/422)) diff --git a/examples/linalg/noxfile.py b/examples/linalg/noxfile.py index 291b0b1a2..6154c9517 100644 --- a/examples/linalg/noxfile.py +++ b/examples/linalg/noxfile.py @@ -3,6 +3,6 @@ @nox.session def tests(session): - session.install("pip", "numpy<2", "pytest") + session.install("pip", "numpy", "pytest") session.run("pip", "install", ".", "-v") session.run("pytest") diff --git a/examples/parallel/noxfile.py b/examples/parallel/noxfile.py index 291b0b1a2..6154c9517 100644 --- a/examples/parallel/noxfile.py +++ b/examples/parallel/noxfile.py @@ -3,6 +3,6 @@ @nox.session def tests(session): - session.install("pip", "numpy<2", "pytest") + session.install("pip", "numpy", "pytest") session.run("pip", "install", ".", "-v") session.run("pytest") diff --git a/examples/simple/noxfile.py b/examples/simple/noxfile.py index 291b0b1a2..6154c9517 100644 --- a/examples/simple/noxfile.py +++ b/examples/simple/noxfile.py @@ -3,6 +3,6 @@ @nox.session def tests(session): - session.install("pip", "numpy<2", "pytest") + session.install("pip", "numpy", "pytest") session.run("pip", "install", ".", "-v") session.run("pytest") diff --git a/src/borrow/shared.rs b/src/borrow/shared.rs index 1ff4344e7..38f8963e5 100644 --- a/src/borrow/shared.rs +++ b/src/borrow/shared.rs @@ -12,7 +12,7 @@ use rustc_hash::FxHashMap; use crate::array::get_array_module; use crate::cold; use crate::error::BorrowError; -use crate::npyffi::{PyArrayObject, PyArray_Check, NPY_ARRAY_WRITEABLE}; +use crate::npyffi::{PyArrayObject, PyArray_Check, PyDataType_ELSIZE, NPY_ARRAY_WRITEABLE}; /// Defines the shared C API used for borrow checking /// @@ -48,7 +48,7 @@ unsafe extern "C" fn acquire_shared(flags: *mut c_void, array: *mut PyArrayObjec let flags = &mut *(flags as *mut BorrowFlags); let address = base_address(py, array); - let key = borrow_key(array); + let key = borrow_key(py, array); match flags.acquire(address, key) { Ok(()) => 0, @@ -66,7 +66,7 @@ unsafe extern "C" fn acquire_mut_shared(flags: *mut c_void, array: *mut PyArrayO let flags = &mut *(flags as *mut BorrowFlags); let address = base_address(py, array); - let key = borrow_key(array); + let key = borrow_key(py, array); match flags.acquire_mut(address, key) { Ok(()) => 0, @@ -80,7 +80,7 @@ unsafe extern "C" fn release_shared(flags: *mut c_void, array: *mut PyArrayObjec let flags = &mut *(flags as *mut BorrowFlags); let address = base_address(py, array); - let key = borrow_key(array); + let key = borrow_key(py, array); flags.release(address, key); } @@ -91,7 +91,7 @@ unsafe extern "C" fn release_mut_shared(flags: *mut c_void, array: *mut PyArrayO let flags = &mut *(flags as *mut BorrowFlags); let address = base_address(py, array); - let key = borrow_key(array); + let key = borrow_key(py, array); flags.release_mut(address, key); } @@ -379,8 +379,8 @@ fn base_address<'py>(py: Python<'py>, mut array: *mut PyArrayObject) -> *mut c_v } } -fn borrow_key(array: *mut PyArrayObject) -> BorrowKey { - let range = data_range(array); +fn borrow_key<'py>(py: Python<'py>, array: *mut PyArrayObject) -> BorrowKey { + let range = data_range(py, array); let data_ptr = unsafe { (*array).data }; let gcd_strides = gcd_strides(array); @@ -392,7 +392,7 @@ fn borrow_key(array: *mut PyArrayObject) -> BorrowKey { } } -fn data_range(array: *mut PyArrayObject) -> (*mut c_char, *mut c_char) { +fn data_range<'py>(py: Python<'py>, array: *mut PyArrayObject) -> (*mut c_char, *mut c_char) { let nd = unsafe { (*array).nd } as usize; let data = unsafe { (*array).data }; @@ -403,7 +403,7 @@ fn data_range(array: *mut PyArrayObject) -> (*mut c_char, *mut c_char) { let shape = unsafe { from_raw_parts((*array).dimensions as *mut usize, nd) }; let strides = unsafe { from_raw_parts((*array).strides, nd) }; - let itemsize = unsafe { (*(*array).descr).elsize } as isize; + let itemsize = unsafe { PyDataType_ELSIZE(py, (*array).descr) } as isize; let mut start = 0; let mut end = 0; @@ -468,7 +468,7 @@ mod tests { let base_address = base_address(py, array.as_array_ptr()); assert_eq!(base_address, array.as_ptr().cast()); - let data_range = data_range(array.as_array_ptr()); + let data_range = data_range(py, array.as_array_ptr()); assert_eq!(data_range.0, array.data() as *mut c_char); assert_eq!(data_range.1, unsafe { array.data().add(6) } as *mut c_char); }); @@ -486,7 +486,7 @@ mod tests { assert_ne!(base_address, array.as_ptr().cast()); assert_eq!(base_address, base.cast::()); - let data_range = data_range(array.as_array_ptr()); + let data_range = data_range(py, array.as_array_ptr()); assert_eq!(data_range.0, array.data().cast::()); assert_eq!(data_range.1, unsafe { array.data().add(6).cast::() @@ -517,7 +517,7 @@ mod tests { assert_ne!(base_address, view.as_ptr().cast::()); assert_eq!(base_address, base.cast::()); - let data_range = data_range(view.as_array_ptr()); + let data_range = data_range(py, view.as_array_ptr()); assert_eq!(data_range.0, array.data() as *mut c_char); assert_eq!(data_range.1, unsafe { array.data().add(4) } as *mut c_char); }); @@ -550,7 +550,7 @@ mod tests { assert_ne!(base_address, array.as_ptr().cast::()); assert_eq!(base_address, base.cast::()); - let data_range = data_range(view.as_array_ptr()); + let data_range = data_range(py, view.as_array_ptr()); assert_eq!(data_range.0, array.data().cast::()); assert_eq!(data_range.1, unsafe { array.data().add(4).cast::() @@ -600,7 +600,7 @@ mod tests { assert_ne!(base_address, view1.as_ptr().cast::()); assert_eq!(base_address, base as *mut c_void); - let data_range = data_range(view2.as_array_ptr()); + let data_range = data_range(py, view2.as_array_ptr()); assert_eq!(data_range.0, array.data() as *mut c_char); assert_eq!(data_range.1, unsafe { array.data().add(1) } as *mut c_char); }); @@ -652,7 +652,7 @@ mod tests { assert_ne!(base_address, array.as_ptr().cast::()); assert_eq!(base_address, base.cast::()); - let data_range = data_range(view2.as_array_ptr()); + let data_range = data_range(py, view2.as_array_ptr()); assert_eq!(data_range.0, array.data().cast::()); assert_eq!(data_range.1, unsafe { array.data().add(1).cast::() @@ -683,7 +683,7 @@ mod tests { assert_ne!(base_address, view.as_ptr().cast::()); assert_eq!(base_address, base.cast::()); - let data_range = data_range(view.as_array_ptr()); + let data_range = data_range(py, view.as_array_ptr()); assert_eq!(view.data(), unsafe { array.data().offset(2) }); assert_eq!(data_range.0, unsafe { view.data().offset(-2) } as *mut c_char); @@ -703,7 +703,7 @@ mod tests { let base_address = base_address(py, array.as_array_ptr()); assert_eq!(base_address, array.as_ptr().cast::()); - let data_range = data_range(array.as_array_ptr()); + let data_range = data_range(py, array.as_array_ptr()); assert_eq!(data_range.0, array.data() as *mut c_char); assert_eq!(data_range.1, array.data() as *mut c_char); }); @@ -721,7 +721,7 @@ mod tests { .downcast_into::>() .unwrap(); - let key1 = borrow_key(view1.as_array_ptr()); + let key1 = borrow_key(py, view1.as_array_ptr()); assert_eq!(view1.strides(), &[80, 24]); assert_eq!(key1.gcd_strides, 8); @@ -732,7 +732,7 @@ mod tests { .downcast_into::>() .unwrap(); - let key2 = borrow_key(view2.as_array_ptr()); + let key2 = borrow_key(py, view2.as_array_ptr()); assert_eq!(view2.strides(), &[80, 24]); assert_eq!(key2.gcd_strides, 8); @@ -743,7 +743,7 @@ mod tests { .downcast_into::>() .unwrap(); - let key3 = borrow_key(view3.as_array_ptr()); + let key3 = borrow_key(py, view3.as_array_ptr()); assert_eq!(view3.strides(), &[80, 16]); assert_eq!(key3.gcd_strides, 16); @@ -754,7 +754,7 @@ mod tests { .downcast_into::>() .unwrap(); - let key4 = borrow_key(view4.as_array_ptr()); + let key4 = borrow_key(py, view4.as_array_ptr()); assert_eq!(view4.strides(), &[80, 16]); assert_eq!(key4.gcd_strides, 16); @@ -777,7 +777,7 @@ mod tests { let base1 = base_address(py, array1.as_array_ptr()); let base2 = base_address(py, array2.as_array_ptr()); - let key1 = borrow_key(array1.as_array_ptr()); + let key1 = borrow_key(py, array1.as_array_ptr()); let _exclusive1 = array1.readwrite(); { @@ -791,7 +791,7 @@ mod tests { assert_eq!(flag, -1); } - let key2 = borrow_key(array2.as_array_ptr()); + let key2 = borrow_key(py, array2.as_array_ptr()); let _shared2 = array2.readonly(); { @@ -827,7 +827,7 @@ mod tests { .downcast_into::>() .unwrap(); - let key1 = borrow_key(view1.as_array_ptr()); + let key1 = borrow_key(py, view1.as_array_ptr()); let exclusive1 = view1.readwrite(); { @@ -847,7 +847,7 @@ mod tests { .downcast_into::>() .unwrap(); - let key2 = borrow_key(view2.as_array_ptr()); + let key2 = borrow_key(py, view2.as_array_ptr()); let shared2 = view2.readonly(); { @@ -870,7 +870,7 @@ mod tests { .downcast_into::>() .unwrap(); - let key3 = borrow_key(view3.as_array_ptr()); + let key3 = borrow_key(py, view3.as_array_ptr()); let shared3 = view3.readonly(); { @@ -896,7 +896,7 @@ mod tests { .downcast_into::>() .unwrap(); - let key4 = borrow_key(view4.as_array_ptr()); + let key4 = borrow_key(py, view4.as_array_ptr()); let shared4 = view4.readonly(); { diff --git a/src/datetime.rs b/src/datetime.rs index 9a96799e5..794170616 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -67,7 +67,9 @@ use pyo3::{sync::GILProtected, Bound, Py, Python}; use rustc_hash::FxHashMap; use crate::dtype::{Element, PyArrayDescr, PyArrayDescrMethods}; -use crate::npyffi::{PyArray_DatetimeDTypeMetaData, NPY_DATETIMEUNIT, NPY_TYPES}; +use crate::npyffi::{ + PyArray_DatetimeDTypeMetaData, PyDataType_C_METADATA, NPY_DATETIMEUNIT, NPY_TYPES, +}; /// Represents the [datetime units][datetime-units] supported by NumPy /// @@ -230,7 +232,7 @@ impl TypeDescriptors { // SAFETY: `self.npy_type` is either `NPY_DATETIME` or `NPY_TIMEDELTA` which implies the type of `c_metadata`. unsafe { - let metadata = &mut *((*dtype.as_dtype_ptr()).c_metadata + let metadata = &mut *(PyDataType_C_METADATA(py, dtype.as_dtype_ptr()) as *mut PyArray_DatetimeDTypeMetaData); metadata.meta.base = unit; diff --git a/src/dtype.rs b/src/dtype.rs index 9aa37eab9..bc68dbaa8 100644 --- a/src/dtype.rs +++ b/src/dtype.rs @@ -1,7 +1,5 @@ use std::mem::size_of; -use std::os::raw::{ - c_char, c_int, c_long, c_longlong, c_short, c_uint, c_ulong, c_ulonglong, c_ushort, -}; +use std::os::raw::{c_int, c_long, c_longlong, c_short, c_uint, c_ulong, c_ulonglong, c_ushort}; use std::ptr; #[cfg(feature = "half")] @@ -19,8 +17,9 @@ use pyo3::{ use pyo3::{sync::GILOnceCell, Py}; use crate::npyffi::{ - NpyTypes, PyArray_Descr, NPY_ALIGNED_STRUCT, NPY_BYTEORDER_CHAR, NPY_ITEM_HASOBJECT, NPY_TYPES, - PY_ARRAY_API, + NpyTypes, PyArray_Descr, PyDataType_ALIGNMENT, PyDataType_ELSIZE, PyDataType_FIELDS, + PyDataType_FLAGS, PyDataType_NAMES, PyDataType_SUBARRAY, NPY_ALIGNED_STRUCT, + NPY_BYTEORDER_CHAR, NPY_ITEM_HASOBJECT, NPY_TYPES, PY_ARRAY_API, }; pub use num_complex::{Complex32, Complex64}; @@ -255,8 +254,11 @@ impl PyArrayDescr { /// /// Equivalent to [`numpy.dtype.flags`][dtype-flags]. /// + /// In numpy 2 the flags field was widened to allow for more flags. + /// /// [dtype-flags]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.flags.html - pub fn flags(&self) -> c_char { + /// [dtype-changes]: https://numpy.org/devdocs/numpy_2_0_migration_guide.html#the-pyarray-descr-struct-has-been-changed + pub fn flags(&self) -> u64 { self.as_borrowed().flags() } @@ -395,19 +397,14 @@ pub trait PyArrayDescrMethods<'py>: Sealed { /// Equivalent to [`numpy.dtype.itemsize`][dtype-itemsize]. /// /// [dtype-itemsiize]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.itemsize.html - - fn itemsize(&self) -> usize { - unsafe { *self.as_dtype_ptr() }.elsize.max(0) as _ - } + fn itemsize(&self) -> usize; /// Returns the required alignment (bytes) of this type descriptor according to the compiler. /// /// Equivalent to [`numpy.dtype.alignment`][dtype-alignment]. /// /// [dtype-alignment]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.alignment.html - fn alignment(&self) -> usize { - unsafe { *self.as_dtype_ptr() }.alignment.max(0) as _ - } + fn alignment(&self) -> usize; /// Returns an ASCII character indicating the byte-order of this type descriptor object. /// @@ -447,21 +444,14 @@ pub trait PyArrayDescrMethods<'py>: Sealed { /// Equivalent to [`numpy.dtype.flags`][dtype-flags]. /// /// [dtype-flags]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.flags.html - fn flags(&self) -> c_char { - unsafe { *self.as_dtype_ptr() }.flags - } + fn flags(&self) -> u64; /// Returns the number of dimensions if this type descriptor represents a sub-array, and zero otherwise. /// /// Equivalent to [`numpy.dtype.ndim`][dtype-ndim]. /// /// [dtype-ndim]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.ndim.html - fn ndim(&self) -> usize { - if !self.has_subarray() { - return 0; - } - unsafe { PyTuple_Size((*((*self.as_dtype_ptr()).subarray)).shape).max(0) as _ } - } + fn ndim(&self) -> usize; /// Returns the type descriptor for the base element of subarrays, regardless of their dimension or shape. /// @@ -503,16 +493,14 @@ pub trait PyArrayDescrMethods<'py>: Sealed { } /// Returns true if the type descriptor is a sub-array. - fn has_subarray(&self) -> bool { - // equivalent to PyDataType_HASSUBARRAY(self) - unsafe { !(*self.as_dtype_ptr()).subarray.is_null() } - } + /// + /// Equivalent to PyDataType_HASSUBARRAY(self). + fn has_subarray(&self) -> bool; /// Returns true if the type descriptor is a structured type. - fn has_fields(&self) -> bool { - // equivalent to PyDataType_HASFIELDS(self) - unsafe { !(*self.as_dtype_ptr()).names.is_null() } - } + /// + /// Equivalent to PyDataType_HASFIELDS(self). + fn has_fields(&self) -> bool; /// Returns true if type descriptor byteorder is native, or `None` if not applicable. fn is_native_byteorder(&self) -> Option { @@ -576,33 +564,63 @@ impl<'py> PyArrayDescrMethods<'py> for Bound<'py, PyArrayDescr> { unsafe { PyType::from_borrowed_type_ptr(self.py(), dtype_type_ptr) } } + fn itemsize(&self) -> usize { + unsafe { PyDataType_ELSIZE(self.py(), self.as_dtype_ptr()).max(0) as _ } + } + + fn alignment(&self) -> usize { + unsafe { PyDataType_ALIGNMENT(self.py(), self.as_dtype_ptr()).max(0) as _ } + } + + fn flags(&self) -> u64 { + unsafe { PyDataType_FLAGS(self.py(), self.as_dtype_ptr()) as _ } + } + + fn ndim(&self) -> usize { + let subarray = unsafe { PyDataType_SUBARRAY(self.py(), self.as_dtype_ptr()).as_ref() }; + match subarray { + None => 0, + Some(subarray) => unsafe { PyTuple_Size(subarray.shape) }.max(0) as _, + } + } + fn base(&self) -> Bound<'py, PyArrayDescr> { - if !self.has_subarray() { - self.clone() - } else { - unsafe { - Bound::from_borrowed_ptr(self.py(), (*(*self.as_dtype_ptr()).subarray).base.cast()) - .downcast_into_unchecked() - } + let subarray = unsafe { PyDataType_SUBARRAY(self.py(), self.as_dtype_ptr()).as_ref() }; + match subarray { + None => self.clone(), + Some(subarray) => unsafe { + Bound::from_borrowed_ptr(self.py(), subarray.base.cast()).downcast_into_unchecked() + }, } } fn shape(&self) -> Vec { - if !self.has_subarray() { - Vec::new() - } else { - // NumPy guarantees that shape is a tuple of non-negative integers so this should never panic. - unsafe { Borrowed::from_ptr(self.py(), (*(*self.as_dtype_ptr()).subarray).shape) } - .extract() - .unwrap() + let subarray = unsafe { PyDataType_SUBARRAY(self.py(), self.as_dtype_ptr()).as_ref() }; + match subarray { + None => Vec::new(), + Some(subarray) => { + // NumPy guarantees that shape is a tuple of non-negative integers so this should never panic. + let shape = unsafe { Borrowed::from_ptr(self.py(), subarray.shape) }; + shape.extract().unwrap() + } } } + fn has_subarray(&self) -> bool { + unsafe { !PyDataType_SUBARRAY(self.py(), self.as_dtype_ptr()).is_null() } + } + + fn has_fields(&self) -> bool { + unsafe { !PyDataType_NAMES(self.py(), self.as_dtype_ptr()).is_null() } + } + fn names(&self) -> Option> { if !self.has_fields() { return None; } - let names = unsafe { Borrowed::from_ptr(self.py(), (*self.as_dtype_ptr()).names) }; + let names = unsafe { + Borrowed::from_ptr(self.py(), PyDataType_NAMES(self.py(), self.as_dtype_ptr())) + }; names.extract().ok() } @@ -612,7 +630,9 @@ impl<'py> PyArrayDescrMethods<'py> for Bound<'py, PyArrayDescr> { "cannot get field information: type descriptor has no fields", )); } - let dict = unsafe { Borrowed::from_ptr(self.py(), (*self.as_dtype_ptr()).fields) }; + let dict = unsafe { + Borrowed::from_ptr(self.py(), PyDataType_FIELDS(self.py(), self.as_dtype_ptr())) + }; let dict = unsafe { dict.downcast_unchecked::() }; // NumPy guarantees that fields are tuples of proper size and type, so this should never panic. let tuple = dict @@ -806,7 +826,7 @@ mod tests { use pyo3::{py_run, types::PyTypeMethods}; - use crate::npyffi::NPY_NEEDS_PYAPI; + use crate::npyffi::{is_numpy_2, NPY_NEEDS_PYAPI}; #[test] fn test_dtype_new() { @@ -835,7 +855,11 @@ mod tests { dtype_bound::(py).typeobj().qualname().unwrap() } Python::with_gil(|py| { - assert_eq!(type_name::(py), "bool_"); + if is_numpy_2(py) { + assert_eq!(type_name::(py), "bool"); + } else { + assert_eq!(type_name::(py), "bool_"); + } assert_eq!(type_name::(py), "int8"); assert_eq!(type_name::(py), "int16"); diff --git a/src/lib.rs b/src/lib.rs index cc218dd18..44dc09622 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,6 +72,9 @@ as well as the [`PyReadonlyArray::try_as_matrix`] and [`PyReadwriteArray::try_as #![deny(missing_docs, missing_debug_implementations)] +#[cfg(all(target_os = "windows", target_arch = "x86"))] +compile_error!("Compilation for 32-bit windows is not currently supported. See https://github.com/PyO3/rust-numpy/issues/448"); + pub mod array; mod array_like; pub mod borrow; diff --git a/src/npyffi/array.rs b/src/npyffi/array.rs index b62c3b22d..7074f61c3 100644 --- a/src/npyffi/array.rs +++ b/src/npyffi/array.rs @@ -68,8 +68,7 @@ impl PyArrayAPI { impl_api![47; PyArray_Zero(arr: *mut PyArrayObject) -> *mut c_char]; impl_api![48; PyArray_One(arr: *mut PyArrayObject) -> *mut c_char]; impl_api![49; PyArray_CastToType(arr: *mut PyArrayObject, dtype: *mut PyArray_Descr, is_f_order: c_int) -> *mut PyObject]; - impl_api![50; PyArray_CastTo(out: *mut PyArrayObject, mp: *mut PyArrayObject) -> c_int]; - impl_api![51; PyArray_CastAnyTo(out: *mut PyArrayObject, mp: *mut PyArrayObject) -> c_int]; + impl_api![50; NumPy1; PyArray_CastTo(out: *mut PyArrayObject, mp: *mut PyArrayObject) -> c_int]; impl_api![52; PyArray_CanCastSafely(fromtype: c_int, totype: c_int) -> c_int]; impl_api![53; PyArray_CanCastTo(from: *mut PyArray_Descr, to: *mut PyArray_Descr) -> npy_bool]; impl_api![54; PyArray_ObjectType(op: *mut PyObject, minimum_type: c_int) -> c_int]; @@ -83,10 +82,11 @@ impl PyArrayAPI { impl_api![62; PyArray_ScalarAsCtype(scalar: *mut PyObject, ctypeptr: *mut c_void)]; impl_api![63; PyArray_CastScalarToCtype(scalar: *mut PyObject, ctypeptr: *mut c_void, outcode: *mut PyArray_Descr) -> c_int]; impl_api![64; PyArray_CastScalarDirect(scalar: *mut PyObject, indescr: *mut PyArray_Descr, ctypeptr: *mut c_void, outtype: c_int) -> c_int]; - impl_api![65; PyArray_ScalarFromObject(object: *mut PyObject) -> *mut PyObject]; - impl_api![66; PyArray_GetCastFunc(descr: *mut PyArray_Descr, type_num: c_int) -> PyArray_VectorUnaryFunc]; - impl_api![67; PyArray_FromDims(nd: c_int, d: *mut c_int, type_: c_int) -> *mut PyObject]; - impl_api![68; PyArray_FromDimsAndDataAndDescr(nd: c_int, d: *mut c_int, descr: *mut PyArray_Descr, data: *mut c_char) -> *mut PyObject]; + impl_api![65; NumPy1; PyArray_ScalarFromObject(object: *mut PyObject) -> *mut PyObject]; + impl_api![65; NumPy2; PyArray_Pack(descr: *mut PyArray_Descr, item: *mut c_void, value: *const PyObject) -> *mut PyObject]; + impl_api![66; NumPy1; PyArray_GetCastFunc(descr: *mut PyArray_Descr, type_num: c_int) -> PyArray_VectorUnaryFunc]; + impl_api![67; NumPy1; PyArray_FromDims(nd: c_int, d: *mut c_int, type_: c_int) -> *mut PyObject]; + impl_api![68; NumPy1; PyArray_FromDimsAndDataAndDescr(nd: c_int, d: *mut c_int, descr: *mut PyArray_Descr, data: *mut c_char) -> *mut PyObject]; impl_api![69; PyArray_FromAny(op: *mut PyObject, newtype: *mut PyArray_Descr, min_depth: c_int, max_depth: c_int, flags: c_int, context: *mut PyObject) -> *mut PyObject]; impl_api![70; PyArray_EnsureArray(op: *mut PyObject) -> *mut PyObject]; impl_api![71; PyArray_EnsureAnyArray(op: *mut PyObject) -> *mut PyObject]; @@ -99,9 +99,7 @@ impl PyArrayAPI { impl_api![78; PyArray_SetField(self_: *mut PyArrayObject, dtype: *mut PyArray_Descr, offset: c_int, val: *mut PyObject) -> c_int]; impl_api![79; PyArray_Byteswap(self_: *mut PyArrayObject, inplace: npy_bool) -> *mut PyObject]; impl_api![80; PyArray_Resize(self_: *mut PyArrayObject, newshape: *mut PyArray_Dims, refcheck: c_int, order: NPY_ORDER) -> *mut PyObject]; - impl_api![81; PyArray_MoveInto(dst: *mut PyArrayObject, src: *mut PyArrayObject) -> c_int]; - impl_api![82; PyArray_CopyInto(dst: *mut PyArrayObject, src: *mut PyArrayObject) -> c_int]; - impl_api![83; PyArray_CopyAnyInto(dst: *mut PyArrayObject, src: *mut PyArrayObject) -> c_int]; + impl_api![81; NumPy1; PyArray_MoveInto(dst: *mut PyArrayObject, src: *mut PyArrayObject) -> c_int]; impl_api![84; PyArray_CopyObject(dest: *mut PyArrayObject, src_object: *mut PyObject) -> c_int]; impl_api![85; PyArray_NewCopy(obj: *mut PyArrayObject, order: NPY_ORDER) -> *mut PyObject]; impl_api![86; PyArray_ToList(self_: *mut PyArrayObject) -> *mut PyObject]; @@ -121,7 +119,7 @@ impl PyArrayAPI { impl_api![100; PyArray_PyIntAsInt(o: *mut PyObject) -> c_int]; impl_api![101; PyArray_PyIntAsIntp(o: *mut PyObject) -> npy_intp]; impl_api![102; PyArray_Broadcast(mit: *mut PyArrayMultiIterObject) -> c_int]; - impl_api![103; PyArray_FillObjectArray(arr: *mut PyArrayObject, obj: *mut PyObject)]; + impl_api![103; NumPy1; PyArray_FillObjectArray(arr: *mut PyArrayObject, obj: *mut PyObject)]; impl_api![104; PyArray_FillWithScalar(arr: *mut PyArrayObject, obj: *mut PyObject) -> c_int]; impl_api![105; PyArray_CheckStrides(elsize: c_int, nd: c_int, numbytes: npy_intp, offset: npy_intp, dims: *mut npy_intp, newstrides: *mut npy_intp) -> npy_bool]; impl_api![106; PyArray_DescrNewByteorder(self_: *mut PyArray_Descr, newendian: c_char) -> *mut PyArray_Descr]; @@ -133,14 +131,14 @@ impl PyArrayAPI { impl_api![112; PyArray_FromArrayAttr(op: *mut PyObject, typecode: *mut PyArray_Descr, context: *mut PyObject) -> *mut PyObject]; impl_api![113; PyArray_ScalarKind(typenum: c_int, arr: *mut *mut PyArrayObject) -> NPY_SCALARKIND]; impl_api![114; PyArray_CanCoerceScalar(thistype: c_int, neededtype: c_int, scalar: NPY_SCALARKIND) -> c_int]; - impl_api![115; PyArray_NewFlagsObject(obj: *mut PyObject) -> *mut PyObject]; + impl_api![115; NumPy1; PyArray_NewFlagsObject(obj: *mut PyObject) -> *mut PyObject]; impl_api![116; PyArray_CanCastScalar(from: *mut PyTypeObject, to: *mut PyTypeObject) -> npy_bool]; - impl_api![117; PyArray_CompareUCS4(s1: *mut npy_ucs4, s2: *mut npy_ucs4, len: usize) -> c_int]; + impl_api![117; NumPy1; PyArray_CompareUCS4(s1: *mut npy_ucs4, s2: *mut npy_ucs4, len: usize) -> c_int]; impl_api![118; PyArray_RemoveSmallest(multi: *mut PyArrayMultiIterObject) -> c_int]; impl_api![119; PyArray_ElementStrides(obj: *mut PyObject) -> c_int]; impl_api![120; PyArray_Item_INCREF(data: *mut c_char, descr: *mut PyArray_Descr)]; impl_api![121; PyArray_Item_XDECREF(data: *mut c_char, descr: *mut PyArray_Descr)]; - impl_api![122; PyArray_FieldNames(fields: *mut PyObject) -> *mut PyObject]; + impl_api![122; NumPy1; PyArray_FieldNames(fields: *mut PyObject) -> *mut PyObject]; impl_api![123; PyArray_Transpose(ap: *mut PyArrayObject, permute: *mut PyArray_Dims) -> *mut PyObject]; impl_api![124; PyArray_TakeFrom(self0: *mut PyArrayObject, indices0: *mut PyObject, axis: c_int, out: *mut PyArrayObject, clipmode: NPY_CLIPMODE) -> *mut PyObject]; impl_api![125; PyArray_PutTo(self_: *mut PyArrayObject, values0: *mut PyObject, indices0: *mut PyObject, clipmode: NPY_CLIPMODE) -> *mut PyObject]; @@ -181,17 +179,17 @@ impl PyArrayAPI { impl_api![160; PyArray_GetPtr(obj: *mut PyArrayObject, ind: *mut npy_intp) -> *mut c_void]; impl_api![161; PyArray_CompareLists(l1: *mut npy_intp, l2: *mut npy_intp, n: c_int) -> c_int]; impl_api![162; PyArray_AsCArray(op: *mut *mut PyObject, ptr: *mut c_void, dims: *mut npy_intp, nd: c_int, typedescr: *mut PyArray_Descr) -> c_int]; - impl_api![163; PyArray_As1D(op: *mut *mut PyObject, ptr: *mut *mut c_char, d1: *mut c_int, typecode: c_int) -> c_int]; - impl_api![164; PyArray_As2D(op: *mut *mut PyObject, ptr: *mut *mut *mut c_char, d1: *mut c_int, d2: *mut c_int, typecode: c_int) -> c_int]; + impl_api![163; NumPy1; PyArray_As1D(op: *mut *mut PyObject, ptr: *mut *mut c_char, d1: *mut c_int, typecode: c_int) -> c_int]; + impl_api![164; NumPy1; PyArray_As2D(op: *mut *mut PyObject, ptr: *mut *mut *mut c_char, d1: *mut c_int, d2: *mut c_int, typecode: c_int) -> c_int]; impl_api![165; PyArray_Free(op: *mut PyObject, ptr: *mut c_void) -> c_int]; impl_api![166; PyArray_Converter(object: *mut PyObject, address: *mut *mut PyObject) -> c_int]; impl_api![167; PyArray_IntpFromSequence(seq: *mut PyObject, vals: *mut npy_intp, maxvals: c_int) -> c_int]; impl_api![168; PyArray_Concatenate(op: *mut PyObject, axis: c_int) -> *mut PyObject]; impl_api![169; PyArray_InnerProduct(op1: *mut PyObject, op2: *mut PyObject) -> *mut PyObject]; impl_api![170; PyArray_MatrixProduct(op1: *mut PyObject, op2: *mut PyObject) -> *mut PyObject]; - impl_api![171; PyArray_CopyAndTranspose(op: *mut PyObject) -> *mut PyObject]; + impl_api![171; NumPy1; PyArray_CopyAndTranspose(op: *mut PyObject) -> *mut PyObject]; impl_api![172; PyArray_Correlate(op1: *mut PyObject, op2: *mut PyObject, mode: c_int) -> *mut PyObject]; - impl_api![173; PyArray_TypestrConvert(itemsize: c_int, gentype: c_int) -> c_int]; + impl_api![173; NumPy1; PyArray_TypestrConvert(itemsize: c_int, gentype: c_int) -> c_int]; impl_api![174; PyArray_DescrConverter(obj: *mut PyObject, at: *mut *mut PyArray_Descr) -> c_int]; impl_api![175; PyArray_DescrConverter2(obj: *mut PyObject, at: *mut *mut PyArray_Descr) -> c_int]; impl_api![176; PyArray_IntpConverter(obj: *mut PyObject, seq: *mut PyArray_Dims) -> c_int]; @@ -210,33 +208,33 @@ impl PyArrayAPI { impl_api![189; PyArray_LexSort(sort_keys: *mut PyObject, axis: c_int) -> *mut PyObject]; impl_api![190; PyArray_Round(a: *mut PyArrayObject, decimals: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![191; PyArray_EquivTypenums(typenum1: c_int, typenum2: c_int) -> c_uchar]; - impl_api![192; PyArray_RegisterDataType(descr: *mut PyArray_Descr) -> c_int]; + impl_api![192; PyArray_RegisterDataType(descr: *mut PyArray_DescrProto) -> c_int]; impl_api![193; PyArray_RegisterCastFunc(descr: *mut PyArray_Descr, totype: c_int, castfunc: PyArray_VectorUnaryFunc) -> c_int]; impl_api![194; PyArray_RegisterCanCast(descr: *mut PyArray_Descr, totype: c_int, scalar: NPY_SCALARKIND) -> c_int]; impl_api![195; PyArray_InitArrFuncs(f: *mut PyArray_ArrFuncs)]; impl_api![196; PyArray_IntTupleFromIntp(len: c_int, vals: *mut npy_intp) -> *mut PyObject]; - impl_api![197; PyArray_ElementFromName(str: *mut c_char) -> c_int]; + impl_api![197; NumPy1; PyArray_ElementFromName(str: *mut c_char) -> c_int]; impl_api![198; PyArray_ClipmodeConverter(object: *mut PyObject, val: *mut NPY_CLIPMODE) -> c_int]; impl_api![199; PyArray_OutputConverter(object: *mut PyObject, address: *mut *mut PyArrayObject) -> c_int]; impl_api![200; PyArray_BroadcastToShape(obj: *mut PyObject, dims: *mut npy_intp, nd: c_int) -> *mut PyObject]; - impl_api![201; _PyArray_SigintHandler(signum: c_int)]; - impl_api![202; _PyArray_GetSigintBuf() -> *mut c_void]; + impl_api![201; NumPy1; _PyArray_SigintHandler(signum: c_int)]; + impl_api![202; NumPy1; _PyArray_GetSigintBuf() -> *mut c_void]; impl_api![203; PyArray_DescrAlignConverter(obj: *mut PyObject, at: *mut *mut PyArray_Descr) -> c_int]; impl_api![204; PyArray_DescrAlignConverter2(obj: *mut PyObject, at: *mut *mut PyArray_Descr) -> c_int]; impl_api![205; PyArray_SearchsideConverter(obj: *mut PyObject, addr: *mut c_void) -> c_int]; impl_api![206; PyArray_CheckAxis(arr: *mut PyArrayObject, axis: *mut c_int, flags: c_int) -> *mut PyObject]; impl_api![207; PyArray_OverflowMultiplyList(l1: *mut npy_intp, n: c_int) -> npy_intp]; - impl_api![208; PyArray_CompareString(s1: *mut c_char, s2: *mut c_char, len: usize) -> c_int]; + impl_api![208; NumPy1; PyArray_CompareString(s1: *mut c_char, s2: *mut c_char, len: usize) -> c_int]; // impl_api![209; PyArray_MultiIterFromObjects(mps: *mut *mut PyObject, n: c_int, nadd: c_int, ...) -> *mut PyObject]; impl_api![210; PyArray_GetEndianness() -> c_int]; impl_api![211; PyArray_GetNDArrayCFeatureVersion() -> c_uint]; impl_api![212; PyArray_Correlate2(op1: *mut PyObject, op2: *mut PyObject, mode: c_int) -> *mut PyObject]; impl_api![213; PyArray_NeighborhoodIterNew(x: *mut PyArrayIterObject, bounds: *mut npy_intp, mode: c_int, fill: *mut PyArrayObject) -> *mut PyObject]; - impl_api![219; PyArray_SetDatetimeParseFunction(op: *mut PyObject)]; - impl_api![220; PyArray_DatetimeToDatetimeStruct(val: npy_datetime, fr: NPY_DATETIMEUNIT, result: *mut npy_datetimestruct)]; - impl_api![221; PyArray_TimedeltaToTimedeltaStruct(val: npy_timedelta, fr: NPY_DATETIMEUNIT, result: *mut npy_timedeltastruct)]; - impl_api![222; PyArray_DatetimeStructToDatetime(fr: NPY_DATETIMEUNIT, d: *mut npy_datetimestruct) -> npy_datetime]; - impl_api![223; PyArray_TimedeltaStructToTimedelta(fr: NPY_DATETIMEUNIT, d: *mut npy_timedeltastruct) -> npy_datetime]; + impl_api![219; NumPy1; PyArray_SetDatetimeParseFunction(op: *mut PyObject)]; + impl_api![220; NumPy1; PyArray_DatetimeToDatetimeStruct(val: npy_datetime, fr: NPY_DATETIMEUNIT, result: *mut npy_datetimestruct)]; + impl_api![221; NumPy1; PyArray_TimedeltaToTimedeltaStruct(val: npy_timedelta, fr: NPY_DATETIMEUNIT, result: *mut npy_timedeltastruct)]; + impl_api![222; NumPy1; PyArray_DatetimeStructToDatetime(fr: NPY_DATETIMEUNIT, d: *mut npy_datetimestruct) -> npy_datetime]; + impl_api![223; NumPy1; PyArray_TimedeltaStructToTimedelta(fr: NPY_DATETIMEUNIT, d: *mut npy_timedeltastruct) -> npy_datetime]; impl_api![224; NpyIter_New(op: *mut PyArrayObject, flags: npy_uint32, order: NPY_ORDER, casting: NPY_CASTING, dtype: *mut PyArray_Descr) -> *mut NpyIter]; impl_api![225; NpyIter_MultiNew(nop: c_int, op_in: *mut *mut PyArrayObject, flags: npy_uint32, order: NPY_ORDER, casting: NPY_CASTING, op_flags: *mut npy_uint32, op_request_dtypes: *mut *mut PyArray_Descr) -> *mut NpyIter]; impl_api![226; NpyIter_AdvancedNew(nop: c_int, op_in: *mut *mut PyArrayObject, flags: npy_uint32, order: NPY_ORDER, casting: NPY_CASTING, op_flags: *mut npy_uint32, op_request_dtypes: *mut *mut PyArray_Descr, oa_ndim: c_int, op_axes: *mut *mut c_int, itershape: *mut npy_intp, buffersize: npy_intp) -> *mut NpyIter]; @@ -291,7 +289,7 @@ impl PyArrayAPI { impl_api![275; PyArray_CanCastTypeTo(from: *mut PyArray_Descr, to: *mut PyArray_Descr, casting: NPY_CASTING) -> npy_bool]; impl_api![276; PyArray_EinsteinSum(subscripts: *mut c_char, nop: npy_intp, op_in: *mut *mut PyArrayObject, dtype: *mut PyArray_Descr, order: NPY_ORDER, casting: NPY_CASTING, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![277; PyArray_NewLikeArray(prototype: *mut PyArrayObject, order: NPY_ORDER, dtype: *mut PyArray_Descr, subok: c_int) -> *mut PyObject]; - impl_api![278; PyArray_GetArrayParamsFromObject(op: *mut PyObject, requested_dtype: *mut PyArray_Descr, writeable: npy_bool, out_dtype: *mut *mut PyArray_Descr, out_ndim: *mut c_int, out_dims: *mut npy_intp, out_arr: *mut *mut PyArrayObject, context: *mut PyObject) -> c_int]; + impl_api![278; NumPy1; PyArray_GetArrayParamsFromObject(op: *mut PyObject, requested_dtype: *mut PyArray_Descr, writeable: npy_bool, out_dtype: *mut *mut PyArray_Descr, out_ndim: *mut c_int, out_dims: *mut npy_intp, out_arr: *mut *mut PyArrayObject, context: *mut PyObject) -> c_int]; impl_api![279; PyArray_ConvertClipmodeSequence(object: *mut PyObject, modes: *mut NPY_CLIPMODE, n: c_int) -> c_int]; impl_api![280; PyArray_MatrixProduct2(op1: *mut PyObject, op2: *mut PyObject, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![281; NpyIter_IsFirstVisit(iter: *mut NpyIter, iop: c_int) -> npy_bool]; @@ -304,18 +302,64 @@ impl PyArrayAPI { impl_api![288; PyDataMem_NEW(size: usize) -> *mut c_void]; impl_api![289; PyDataMem_FREE(ptr: *mut c_void)]; impl_api![290; PyDataMem_RENEW(ptr: *mut c_void, size: usize) -> *mut c_void]; - impl_api![291; PyDataMem_SetEventHook(newhook: PyDataMem_EventHookFunc, user_data: *mut c_void, old_data: *mut *mut c_void) -> PyDataMem_EventHookFunc]; - impl_api![293; PyArray_MapIterSwapAxes(mit: *mut PyArrayMapIterObject, ret: *mut *mut PyArrayObject, getmap: c_int)]; - impl_api![294; PyArray_MapIterArray(a: *mut PyArrayObject, index: *mut PyObject) -> *mut PyObject]; - impl_api![295; PyArray_MapIterNext(mit: *mut PyArrayMapIterObject)]; + impl_api![291; NumPy1; PyDataMem_SetEventHook(newhook: PyDataMem_EventHookFunc, user_data: *mut c_void, old_data: *mut *mut c_void) -> PyDataMem_EventHookFunc]; + impl_api![293; NumPy1; PyArray_MapIterSwapAxes(mit: *mut PyArrayMapIterObject, ret: *mut *mut PyArrayObject, getmap: c_int)]; + impl_api![294; NumPy1; PyArray_MapIterArray(a: *mut PyArrayObject, index: *mut PyObject) -> *mut PyObject]; + impl_api![295; NumPy1; PyArray_MapIterNext(mit: *mut PyArrayMapIterObject)]; impl_api![296; PyArray_Partition(op: *mut PyArrayObject, ktharray: *mut PyArrayObject, axis: c_int, which: NPY_SELECTKIND) -> c_int]; impl_api![297; PyArray_ArgPartition(op: *mut PyArrayObject, ktharray: *mut PyArrayObject, axis: c_int, which: NPY_SELECTKIND) -> *mut PyObject]; impl_api![298; PyArray_SelectkindConverter(obj: *mut PyObject, selectkind: *mut NPY_SELECTKIND) -> c_int]; impl_api![299; PyDataMem_NEW_ZEROED(size: usize, elsize: usize) -> *mut c_void]; impl_api![300; PyArray_CheckAnyScalarExact(obj: *mut PyObject) -> c_int]; - impl_api![301; PyArray_MapIterArrayCopyIfOverlap(a: *mut PyArrayObject, index: *mut PyObject, copy_if_overlap: c_int, extra_op: *mut PyArrayObject) -> *mut PyObject]; + impl_api![301; NumPy1; PyArray_MapIterArrayCopyIfOverlap(a: *mut PyArrayObject, index: *mut PyObject, copy_if_overlap: c_int, extra_op: *mut PyArrayObject) -> *mut PyObject]; impl_api![302; PyArray_ResolveWritebackIfCopy(self_: *mut PyArrayObject) -> c_int]; impl_api![303; PyArray_SetWritebackIfCopyBase(arr: *mut PyArrayObject, base: *mut PyArrayObject) -> c_int]; + impl_api![304; PyDataMem_SetHandler(handler: *mut PyObject) -> *mut PyObject]; + impl_api![305; PyDataMem_GetHandler() -> *mut PyObject]; + impl_api![307; NumPy2; NpyDatetime_ConvertDatetime64ToDatetimeStruct(meta: *mut PyArray_DatetimeMetaData, dt: npy_datetime, out: *mut npy_datetimestruct) -> c_int]; + impl_api![308; NumPy2; NpyDatetime_ConvertDatetimeStructToDatetime64(meta: *mut PyArray_DatetimeMetaData, dts: *const npy_datetimestruct, out: *mut npy_datetime) -> c_int]; + impl_api![309; NumPy2; NpyDatetime_ConvertPyDateTimeToDatetimeStruct(obj: *mut PyObject, out: *mut npy_datetimestruct, out_bestunit: *mut NPY_DATETIMEUNIT, apply_tzinfo: c_int) -> c_int]; + impl_api![310; NumPy2; NpyDatetime_GetDatetimeISO8601StrLen(local: c_int, base: NPY_DATETIMEUNIT) -> c_int]; + impl_api![311; NumPy2; NpyDatetime_MakeISO8601Datetime(dts: *mut npy_datetimestruct, outstr: *mut c_char, outlen: npy_intp, local: c_int, utc: c_int, base: NPY_DATETIMEUNIT, tzoffset: c_int, casting: NPY_CASTING) -> c_int]; + impl_api![312; NumPy2; NpyDatetime_ParseISO8601Datetime(str: *const c_char, len: pyo3::ffi::Py_ssize_t, unit: NPY_DATETIMEUNIT, casting: NPY_CASTING, out: *mut npy_datetimestruct, out_bestunit: *mut NPY_DATETIMEUNIT, out_special: *mut npy_bool) -> c_int]; + impl_api![313; NumPy2; NpyString_load(allocator: *mut npy_string_allocator, packed_string: *const npy_packed_static_string, unpacked_string: *mut npy_static_string) -> c_int]; + impl_api![314; NumPy2; NpyString_pack(out: *mut npy_packed_static_string) -> c_int]; + impl_api![315; NumPy2; NpyString_pack_null(allocator: *mut npy_string_allocator, packed_string: *mut npy_packed_static_string) -> c_int]; + impl_api![316; NumPy2; NpyString_acquire_allocator(descr: *const PyArray_StringDTypeObject) -> *mut npy_string_allocator]; + impl_api![317; NumPy2; NpyString_acquire_allocators(n_descriptors: usize, descrs: *const *mut PyArray_Descr, allocators: *mut *mut npy_string_allocator)]; + impl_api![318; NumPy2; NpyString_release_allocator(allocator: *mut npy_string_allocator)]; + impl_api![319; NumPy2; NpyString_release_allocators(length: usize, allocators: *mut *mut npy_string_allocator)]; + impl_api![361; NumPy2; PyArray_GetDefaultDescr(DType: *mut PyArray_DTypeMeta) -> *mut PyArray_Descr]; + impl_api![362; NumPy2; PyArrayInitDTypeMeta_FromSpec(DType: *mut PyArray_DTypeMeta, spec: *mut PyArrayDTypeMeta_Spec) -> c_int]; + impl_api![363; NumPy2; PyArray_CommonDType(dtype1: *mut PyArray_DTypeMeta, dtype2: *mut PyArray_DTypeMeta) -> PyArray_DTypeMeta]; + impl_api![364; NumPy2; PyArray_PromoteDTypeSequence(length: npy_intp, dtypes_in: *mut *mut PyArray_DTypeMeta) -> *mut PyArray_DTypeMeta]; + impl_api![365; NumPy2; _PyDataType_GetArrFuncs(descr: *const PyArray_Descr) -> *mut PyArray_ArrFuncs]; + + #[allow(non_snake_case)] + pub unsafe fn PyArray_CopyInto<'py>( + &self, + py: Python<'py>, + dst: *mut PyArrayObject, + src: *mut PyArrayObject, + ) -> c_int { + let offset = if is_numpy_2(py) { 50 } else { 82 }; + let fptr = self.get(py, offset) + as *const extern "C" fn(dst: *mut PyArrayObject, src: *mut PyArrayObject) -> c_int; + (*fptr)(dst, src) + } + + #[allow(non_snake_case)] + pub unsafe fn PyArray_CastAnyTo<'py>( + &self, + py: Python<'py>, + out: *mut PyArrayObject, + mp: *mut PyArrayObject, + ) -> c_int { + let offset = if is_numpy_2(py) { 51 } else { 83 }; + let fptr = self.get(py, offset) + as *const extern "C" fn(out: *mut PyArrayObject, mp: *mut PyArrayObject) -> c_int; + (*fptr)(out, mp) + } } // Define type objects associated with the NumPy API diff --git a/src/npyffi/flags.rs b/src/npyffi/flags.rs index 7c9dedb6e..f39d5cc11 100644 --- a/src/npyffi/flags.rs +++ b/src/npyffi/flags.rs @@ -1,4 +1,4 @@ -use super::{npy_char, npy_uint32}; +use super::npy_uint32; use std::os::raw::c_int; pub const NPY_ARRAY_C_CONTIGUOUS: c_int = 0x0001; @@ -63,19 +63,19 @@ pub const NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE: npy_uint32 = 0x40000000; pub const NPY_ITER_GLOBAL_FLAGS: npy_uint32 = 0x0000ffff; pub const NPY_ITER_PER_OP_FLAGS: npy_uint32 = 0xffff0000; -pub const NPY_ITEM_REFCOUNT: npy_char = 0x01; -pub const NPY_ITEM_HASOBJECT: npy_char = 0x01; -pub const NPY_LIST_PICKLE: npy_char = 0x02; -pub const NPY_ITEM_IS_POINTER: npy_char = 0x04; -pub const NPY_NEEDS_INIT: npy_char = 0x08; -pub const NPY_NEEDS_PYAPI: npy_char = 0x10; -pub const NPY_USE_GETITEM: npy_char = 0x20; -pub const NPY_USE_SETITEM: npy_char = 0x40; +pub const NPY_ITEM_REFCOUNT: u64 = 0x01; +pub const NPY_ITEM_HASOBJECT: u64 = 0x01; +pub const NPY_LIST_PICKLE: u64 = 0x02; +pub const NPY_ITEM_IS_POINTER: u64 = 0x04; +pub const NPY_NEEDS_INIT: u64 = 0x08; +pub const NPY_NEEDS_PYAPI: u64 = 0x10; +pub const NPY_USE_GETITEM: u64 = 0x20; +pub const NPY_USE_SETITEM: u64 = 0x40; #[allow(overflowing_literals)] -pub const NPY_ALIGNED_STRUCT: npy_char = 0x80; -pub const NPY_FROM_FIELDS: npy_char = +pub const NPY_ALIGNED_STRUCT: u64 = 0x80; +pub const NPY_FROM_FIELDS: u64 = NPY_NEEDS_INIT | NPY_LIST_PICKLE | NPY_ITEM_REFCOUNT | NPY_NEEDS_PYAPI; -pub const NPY_OBJECT_DTYPE_FLAGS: npy_char = NPY_LIST_PICKLE +pub const NPY_OBJECT_DTYPE_FLAGS: u64 = NPY_LIST_PICKLE | NPY_USE_GETITEM | NPY_ITEM_IS_POINTER | NPY_ITEM_REFCOUNT diff --git a/src/npyffi/mod.rs b/src/npyffi/mod.rs index 1f71f2ca5..8a75dff8b 100644 --- a/src/npyffi/mod.rs +++ b/src/npyffi/mod.rs @@ -10,13 +10,18 @@ )] use std::mem::forget; -use std::os::raw::c_void; +use std::os::raw::{c_uint, c_void}; use pyo3::{ + sync::GILOnceCell, types::{PyAnyMethods, PyCapsule, PyCapsuleMethods, PyModule}, PyResult, Python, }; +pub const API_VERSION_2_0: c_uint = 0x00000012; + +static API_VERSION: GILOnceCell = GILOnceCell::new(); + fn get_numpy_api<'py>( py: Python<'py>, module: &str, @@ -34,16 +39,56 @@ fn get_numpy_api<'py>( Ok(api) } +/// Returns whether the runtime `numpy` version is 2.0 or greater. +pub fn is_numpy_2<'py>(py: Python<'py>) -> bool { + let api_version = *API_VERSION.get_or_init(py, || unsafe { + PY_ARRAY_API.PyArray_GetNDArrayCFeatureVersion(py) + }); + api_version >= API_VERSION_2_0 +} + // Implements wrappers for NumPy's Array and UFunc API macro_rules! impl_api { - [$offset: expr; $fname: ident ( $($arg: ident : $t: ty),* $(,)?) $( -> $ret: ty )* ] => { + // API available on all versions + [$offset: expr; $fname: ident ($($arg: ident: $t: ty),* $(,)?) $(-> $ret: ty)?] => { #[allow(non_snake_case)] - pub unsafe fn $fname<'py>(&self, py: Python<'py>, $($arg : $t), *) $( -> $ret )* { - let fptr = self.get(py, $offset) - as *const extern fn ($($arg : $t), *) $( -> $ret )*; + pub unsafe fn $fname<'py>(&self, py: Python<'py>, $($arg : $t), *) $(-> $ret)* { + let fptr = self.get(py, $offset) as *const extern fn ($($arg : $t), *) $(-> $ret)*; (*fptr)($($arg), *) } }; + + // API with version constraints, checked at runtime + [$offset: expr; NumPy1; $fname: ident ($($arg: ident: $t: ty),* $(,)?) $(-> $ret: ty)?] => { + #[allow(non_snake_case)] + pub unsafe fn $fname<'py>(&self, py: Python<'py>, $($arg : $t), *) $(-> $ret)* { + assert!( + !is_numpy_2(py), + "{} requires API < {:08X} (NumPy 1) but the runtime version is API {:08X}", + stringify!($fname), + API_VERSION_2_0, + *API_VERSION.get(py).expect("API_VERSION is initialized"), + ); + let fptr = self.get(py, $offset) as *const extern fn ($($arg: $t), *) $(-> $ret)*; + (*fptr)($($arg), *) + } + + }; + [$offset: expr; NumPy2; $fname: ident ($($arg: ident: $t: ty),* $(,)?) $(-> $ret: ty)?] => { + #[allow(non_snake_case)] + pub unsafe fn $fname<'py>(&self, py: Python<'py>, $($arg : $t), *) $(-> $ret)* { + assert!( + is_numpy_2(py), + "{} requires API {:08X} or greater (NumPy 2) but the runtime version is API {:08X}", + stringify!($fname), + API_VERSION_2_0, + *API_VERSION.get(py).expect("API_VERSION is initialized"), + ); + let fptr = self.get(py, $offset) as *const extern fn ($($arg: $t), *) $(-> $ret)*; + (*fptr)($($arg), *) + } + + }; } pub mod array; diff --git a/src/npyffi/objects.rs b/src/npyffi/objects.rs index 6b252bcba..fa0fb403c 100644 --- a/src/npyffi/objects.rs +++ b/src/npyffi/objects.rs @@ -8,6 +8,7 @@ use pyo3::ffi::*; use std::os::raw::*; use super::types::*; +use crate::npyffi::*; #[repr(C)] #[derive(Copy, Clone)] @@ -26,6 +27,18 @@ pub struct PyArrayObject { #[repr(C)] #[derive(Copy, Clone)] pub struct PyArray_Descr { + pub ob_base: PyObject, + pub typeobj: *mut PyTypeObject, + pub kind: c_char, + pub type_: c_char, + pub byteorder: c_char, + pub _former_flags: c_char, + pub type_num: c_int, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct PyArray_DescrProto { pub ob_base: PyObject, pub typeobj: *mut PyTypeObject, pub kind: c_char, @@ -44,6 +57,136 @@ pub struct PyArray_Descr { pub hash: npy_hash_t, } +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _PyArray_DescrNumPy2 { + pub ob_base: PyObject, + pub typeobj: *mut PyTypeObject, + pub kind: c_char, + pub type_: c_char, + pub byteorder: c_char, + pub _former_flags: c_char, + pub type_num: c_int, + pub flags: npy_uint64, + pub elsize: npy_intp, + pub alignment: npy_intp, + pub metadata: *mut PyObject, + pub hash: npy_hash_t, + pub reserved_null: [*mut std::ffi::c_void; 2], +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct _PyArray_LegacyDescr { + pub ob_base: PyObject, + pub typeobj: *mut PyTypeObject, + pub kind: c_char, + pub type_: c_char, + pub byteorder: c_char, + pub _former_flags: c_char, + pub type_num: c_int, + pub flags: npy_uint64, + pub elsize: npy_intp, + pub alignment: npy_intp, + pub metadata: *mut PyObject, + pub hash: npy_hash_t, + pub reserved_null: [*mut std::ffi::c_void; 2], + pub subarray: *mut PyArray_ArrayDescr, + pub fields: *mut PyObject, + pub names: *mut PyObject, + pub c_metadata: *mut NpyAuxData, +} + +#[allow(non_snake_case)] +#[inline(always)] +pub unsafe fn PyDataType_ISLEGACY(dtype: *const PyArray_Descr) -> bool { + (*dtype).type_num < NPY_TYPES::NPY_VSTRING as _ && (*dtype).type_num >= 0 +} + +#[allow(non_snake_case)] +#[inline(always)] +pub unsafe fn PyDataType_SET_ELSIZE<'py>( + py: Python<'py>, + dtype: *mut PyArray_Descr, + size: npy_intp, +) { + if is_numpy_2(py) { + unsafe { + (*(dtype as *mut _PyArray_DescrNumPy2)).elsize = size; + } + } else { + unsafe { + (*(dtype as *mut PyArray_DescrProto)).elsize = size as c_int; + } + } +} + +#[allow(non_snake_case)] +#[inline(always)] +pub unsafe fn PyDataType_FLAGS<'py>(py: Python<'py>, dtype: *const PyArray_Descr) -> npy_uint64 { + if is_numpy_2(py) { + unsafe { (*(dtype as *mut _PyArray_DescrNumPy2)).flags } + } else { + unsafe { (*(dtype as *mut PyArray_DescrProto)).flags as c_uchar as npy_uint64 } + } +} + +macro_rules! define_descr_accessor { + ($name:ident, $property:ident, $type:ty, $legacy_only:literal, $default:expr) => { + #[allow(non_snake_case)] + #[inline(always)] + pub unsafe fn $name<'py>(py: Python<'py>, dtype: *const PyArray_Descr) -> $type { + if $legacy_only && !PyDataType_ISLEGACY(dtype) { + $default + } else { + if is_numpy_2(py) { + unsafe { (*(dtype as *const _PyArray_LegacyDescr)).$property } + } else { + unsafe { (*(dtype as *mut PyArray_DescrProto)).$property as $type } + } + } + } + }; +} + +define_descr_accessor!(PyDataType_ELSIZE, elsize, npy_intp, false, 0); +define_descr_accessor!(PyDataType_ALIGNMENT, alignment, npy_intp, false, 0); +define_descr_accessor!( + PyDataType_METADATA, + metadata, + *mut PyObject, + true, + std::ptr::null_mut() +); +define_descr_accessor!( + PyDataType_SUBARRAY, + subarray, + *mut PyArray_ArrayDescr, + true, + std::ptr::null_mut() +); +define_descr_accessor!( + PyDataType_NAMES, + names, + *mut PyObject, + true, + std::ptr::null_mut() +); +define_descr_accessor!( + PyDataType_FIELDS, + fields, + *mut PyObject, + true, + std::ptr::null_mut() +); +define_descr_accessor!( + PyDataType_C_METADATA, + c_metadata, + *mut NpyAuxData, + true, + std::ptr::null_mut() +); + #[repr(C)] #[derive(Copy, Clone)] pub struct PyArray_ArrayDescr { @@ -412,3 +555,53 @@ pub struct PyArray_DatetimeDTypeMetaData { pub base: NpyAuxData, pub meta: PyArray_DatetimeMetaData, } + +// npy_packed_static_string and npy_string_allocator are opaque pointers. +// FIXME(adamreichold): Consider extern types when they are stabilized. +// https://github.com/rust-lang/rust/issues/43467 +pub type npy_packed_static_string = c_void; +pub type npy_string_allocator = c_void; +pub type PyArray_DTypeMeta = PyTypeObject; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct npy_static_string { + pub size: usize, + pub buf: *const c_char, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct PyArray_StringDTypeObject { + pub base: PyArray_Descr, + pub na_object: *mut PyObject, + pub coerce: c_char, + pub has_nan_na: c_char, + pub has_string_na: c_char, + pub array_owned: c_char, + pub default_string: npy_static_string, + pub na_name: npy_static_string, + pub allocator: *mut npy_string_allocator, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct PyArrayMethod_Spec { + pub name: *const c_char, + pub nin: c_int, + pub nout: c_int, + pub casting: NPY_CASTING, + pub flags: NPY_ARRAYMETHOD_FLAGS, + pub dtypes: *mut *mut PyArray_DTypeMeta, + pub slots: *mut PyType_Slot, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct PyArrayDTypeMeta_Spec { + pub typeobj: *mut PyTypeObject, + pub flags: c_int, + pub casts: *mut *mut PyArrayMethod_Spec, + pub slots: *mut PyType_Slot, + pub baseclass: *mut PyTypeObject, +} diff --git a/src/npyffi/types.rs b/src/npyffi/types.rs index e147576fd..68df9c52f 100644 --- a/src/npyffi/types.rs +++ b/src/npyffi/types.rs @@ -141,10 +141,11 @@ pub enum NPY_TYPES { NPY_DATETIME = 21, NPY_TIMEDELTA = 22, NPY_HALF = 23, - NPY_NTYPES = 24, + NPY_NTYPES_LEGACY = 24, NPY_NOTYPE = 25, NPY_CHAR = 26, NPY_USERDEF = 256, + NPY_VSTRING = 2056, } #[repr(u32)] @@ -270,3 +271,14 @@ impl NPY_BYTEORDER_CHAR { #[cfg(target_endian = "big")] pub const NPY_OPPBYTE: Self = Self::NPY_LITTLE; } + +#[repr(i32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum NPY_ARRAYMETHOD_FLAGS { + NPY_METH_REQUIRES_PYAPI = 1 << 0, + NPY_METH_NO_FLOATINGPOINT_ERRORS = 1 << 1, + NPY_METH_SUPPORTS_UNALIGNED = 1 << 2, + NPY_METH_IS_REORDERABLE = 1 << 3, + _NPY_METH_FORCE_CAST_INPUTS = 1 << 17, + NPY_METH_RUNTIME_FLAGS = (1 << 0) | (1 << 1), // NPY_METH_REQUIRES_PYAPI | NPY_METH_NO_FLOATINGPOINT_ERRORS +} diff --git a/src/strings.rs b/src/strings.rs index 74606289e..1f440fbda 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -18,6 +18,7 @@ use pyo3::{ use rustc_hash::FxHashMap; use crate::dtype::{Element, PyArrayDescr, PyArrayDescrMethods}; +use crate::npyffi::PyDataType_SET_ELSIZE; use crate::npyffi::NPY_TYPES; /// A newtype wrapper around [`[u8; N]`][Py_UCS1] to handle [`byte` scalars][numpy-bytes] while satisfying coherence. @@ -183,7 +184,7 @@ impl TypeDescriptors { let dtype = PyArrayDescr::new_from_npy_type(py, npy_type); let descr = &mut *dtype.as_dtype_ptr(); - descr.elsize = size.try_into().unwrap(); + PyDataType_SET_ELSIZE(py, descr, size.try_into().unwrap()); descr.byteorder = byteorder; entry.insert(dtype.into())