diff --git a/guide/src/conversions/tables.md b/guide/src/conversions/tables.md index b7a67ef3a82..8680faa55c9 100644 --- a/guide/src/conversions/tables.md +++ b/guide/src/conversions/tables.md @@ -29,7 +29,7 @@ The table below contains the Python type and the corresponding function argument | `slice` | - | `PySlice` | | `type` | - | `PyType` | | `module` | - | `PyModule` | -| `collections.abc.Buffer` | - | `PyBuffer` | +| `collections.abc.Buffer` | - | `PyBuffer`, `PyBufferView` | | `datetime.datetime` | `SystemTime`, `chrono::DateTime`[^7], `chrono::NaiveDateTime`[^7] | `PyDateTime` | | `datetime.date` | `chrono::NaiveDate`[^7] | `PyDate` | | `datetime.time` | `chrono::NaiveTime`[^7] | `PyTime` | diff --git a/newsfragments/5894.added.md b/newsfragments/5894.added.md new file mode 100644 index 00000000000..85c1695022f --- /dev/null +++ b/newsfragments/5894.added.md @@ -0,0 +1 @@ +Add `PyBufferView` and `PyUntypedBufferView`, stack-allocated alternatives to `PyBuffer` and `PyUntypedBuffer` with a scoped closure API that avoids heap allocation. diff --git a/src/buffer.rs b/src/buffer.rs index a6b185e2f57..5ead4243e89 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -35,6 +35,12 @@ use core::ptr::NonNull; use core::{cell, mem, ptr, slice}; use core::{ffi::CStr, fmt::Debug}; +mod flags; + +use self::flags::{ + CONTIGUITY_C, CONTIGUITY_UNDEFINED, PyBufferFlags as RequestFlags, PyBufferRequestType, +}; + /// A typed form of [`PyUntypedBuffer`]. #[repr(transparent)] pub struct PyBuffer(PyUntypedBuffer, PhantomData<[T]>); @@ -58,24 +64,20 @@ struct RawBuffer(ffi::Py_buffer, PhantomPinned); unsafe impl Send for PyUntypedBuffer {} unsafe impl Sync for PyUntypedBuffer {} -impl Debug for PyBuffer { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - debug_buffer("PyBuffer", &self.0, f) - } -} - -impl Debug for PyUntypedBuffer { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - debug_buffer("PyUntypedBuffer", self, f) - } -} - fn debug_buffer( name: &str, - b: &PyUntypedBuffer, + raw: &ffi::Py_buffer, f: &mut core::fmt::Formatter<'_>, ) -> core::fmt::Result { - let raw = b.raw(); + let ndim = raw.ndim as usize; + let format = NonNull::new(raw.format).map(|p| unsafe { CStr::from_ptr(p.as_ptr()) }); + let shape = NonNull::new(raw.shape) + .map(|p| unsafe { slice::from_raw_parts(p.as_ptr().cast::(), ndim) }); + let strides = + NonNull::new(raw.strides).map(|p| unsafe { slice::from_raw_parts(p.as_ptr(), ndim) }); + let suboffsets = + NonNull::new(raw.suboffsets).map(|p| unsafe { slice::from_raw_parts(p.as_ptr(), ndim) }); + f.debug_struct(name) .field("buf", &raw.buf) .field("obj", &raw.obj) @@ -83,14 +85,26 @@ fn debug_buffer( .field("itemsize", &raw.itemsize) .field("readonly", &raw.readonly) .field("ndim", &raw.ndim) - .field("format", &b.format()) - .field("shape", &b.shape()) - .field("strides", &b.strides()) - .field("suboffsets", &b.suboffsets()) + .field("format", &format) + .field("shape", &shape) + .field("strides", &strides) + .field("suboffsets", &suboffsets) .field("internal", &raw.internal) .finish() } +impl Debug for PyBuffer { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + debug_buffer("PyBuffer", self.raw(), f) + } +} + +impl Debug for PyUntypedBuffer { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + debug_buffer("PyUntypedBuffer", self.raw(), f) + } +} + /// Represents the type of a Python buffer element. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ElementType { @@ -350,25 +364,25 @@ impl PyBuffer { /// /// Returns null if the buffer is not C-style contiguous pub fn as_slice_ptr(&self) -> Option> { - self.slice_pointer_internal(self.is_c_contiguous()) + self.is_c_contiguous() + .then(|| self.slice_pointer_internal()) + .flatten() } /// Gets the buffer memory as a slice. /// /// This function returns null if the buffer is not Fortran-style contiguous pub fn as_fortran_slice_ptr(&self) -> Option> { - self.slice_pointer_internal(self.is_fortran_contiguous()) + self.is_fortran_contiguous() + .then(|| self.slice_pointer_internal()) + .flatten() } - fn slice_pointer_internal(&self, contiguous: bool) -> Option> { - if contiguous { - NonNull::new(ptr::slice_from_raw_parts_mut( - self.raw().buf.cast(), - self.item_count(), - )) - } else { - None - } + fn slice_pointer_internal(&self) -> Option> { + NonNull::new(ptr::slice_from_raw_parts_mut( + self.raw().buf.cast(), + self.item_count(), + )) } /// Copies the buffer elements to the specified slice. @@ -841,6 +855,521 @@ impl_element!(isize, SignedInteger); impl_element!(f32, Float); impl_element!(f64, Float); +/// Contiguity constraint encoded by a [`PyBufferRequest`]. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PyBufferContiguity { + /// No contiguity constraint was requested. + Undefined = 0, + /// C-contiguous layout was requested. + C = 1, + /// Fortran-contiguous layout was requested. + F = 2, + /// Either C- or Fortran-contiguous layout was requested. + Any = 3, +} + +/// Type-safe buffer request. The state parameter is intentionally hidden +/// behind this wrapper so the internal encoding can evolve. +/// +/// The requested flags constrain what exporters are allowed to return. For example, +/// without shape information, only 1-dimensional buffers are permitted, and accessors +/// for unrequested metadata are unavailable on the typed view. +pub struct PyBufferRequest< + Flag: PyBufferRequestType = RequestFlags< + false, + false, + false, + false, + false, + CONTIGUITY_UNDEFINED, + >, +>(c_int, PhantomData); + +impl PyBufferRequest { + /// Request format information. + pub const fn format(self) -> PyBufferRequest + where + Flag: flags::CanRequestFormat, + { + PyBufferRequest(self.0 | ffi::PyBUF_FORMAT, PhantomData) + } + + /// Request shape information. + pub const fn nd(self) -> PyBufferRequest + where + Flag: flags::CanRequestShape, + { + PyBufferRequest(self.0 | ffi::PyBUF_ND, PhantomData) + } + + /// Request strides information. Implies shape. + pub const fn strides(self) -> PyBufferRequest + where + Flag: flags::CanRequestStrides, + { + PyBufferRequest(self.0 | ffi::PyBUF_STRIDES, PhantomData) + } + + /// Request suboffsets (indirect). Implies shape and strides. + pub const fn indirect(self) -> PyBufferRequest + where + Flag: flags::CanRequestIndirect, + { + PyBufferRequest(self.0 | ffi::PyBUF_INDIRECT, PhantomData) + } + + /// Request a writable buffer. + pub const fn writable(self) -> PyBufferRequest + where + Flag: flags::CanRequestWritable, + { + PyBufferRequest(self.0 | ffi::PyBUF_WRITABLE, PhantomData) + } + + /// Require C-contiguous layout. Implies shape and strides. + pub const fn c_contiguous(self) -> PyBufferRequest + where + Flag: flags::CanRequestContiguity, + { + PyBufferRequest(self.0 | ffi::PyBUF_C_CONTIGUOUS, PhantomData) + } + + /// Require Fortran-contiguous layout. Implies shape and strides. + pub const fn f_contiguous(self) -> PyBufferRequest + where + Flag: flags::CanRequestContiguity, + { + PyBufferRequest(self.0 | ffi::PyBUF_F_CONTIGUOUS, PhantomData) + } + + /// Require contiguous layout (C or Fortran). Implies shape and strides. + /// + /// The specific contiguity order is not known at compile time, + /// so this does not unlock non-Option slice accessors. + pub const fn any_contiguous(self) -> PyBufferRequest + where + Flag: flags::CanRequestContiguity, + { + PyBufferRequest(self.0 | ffi::PyBUF_ANY_CONTIGUOUS, PhantomData) + } +} + +impl PyBufferRequest { + /// Create a base buffer request. Chain builder methods to add flags. + pub const fn simple() + -> PyBufferRequest> { + PyBufferRequest(ffi::PyBUF_SIMPLE, PhantomData) + } + + /// Create a writable request for all buffer information including suboffsets. + pub const fn full() + -> PyBufferRequest> { + PyBufferRequest(ffi::PyBUF_FULL, PhantomData) + } + + /// Create a read-only request for all buffer information including suboffsets. + pub const fn full_ro() + -> PyBufferRequest> { + PyBufferRequest(ffi::PyBUF_FULL_RO, PhantomData) + } + + /// Create a writable request for format, shape, and strides. + pub const fn records() + -> PyBufferRequest> { + PyBufferRequest(ffi::PyBUF_RECORDS, PhantomData) + } + + /// Create a read-only request for format, shape, and strides. + pub const fn records_ro() + -> PyBufferRequest> { + PyBufferRequest(ffi::PyBUF_RECORDS_RO, PhantomData) + } + + /// Create a writable request for shape and strides. + pub const fn strided() + -> PyBufferRequest> { + PyBufferRequest(ffi::PyBUF_STRIDED, PhantomData) + } + + /// Create a read-only request for shape and strides. + pub const fn strided_ro() + -> PyBufferRequest> { + PyBufferRequest(ffi::PyBUF_STRIDED_RO, PhantomData) + } + + /// Create a writable C-contiguous request. + pub const fn contig() + -> PyBufferRequest> { + PyBufferRequest(ffi::PyBUF_CONTIG, PhantomData) + } + + /// Create a read-only C-contiguous request. + pub const fn contig_ro() + -> PyBufferRequest> { + PyBufferRequest(ffi::PyBUF_CONTIG_RO, PhantomData) + } +} + +/// A typed form of [`PyUntypedBufferView`]. Not constructible directly — use +/// [`PyBufferView::with()`] or [`PyBufferView::with_flags()`]. +#[repr(transparent)] +pub struct PyBufferView< + T, + Flag: PyBufferRequestType = RequestFlags, +>(PyUntypedBufferView, PhantomData<[T]>); + +/// Stack-allocated untyped buffer view. +/// +/// Unlike [`PyUntypedBuffer`] which heap-allocates, this places the `Py_buffer` on the +/// stack. The scoped closure API ensures the buffer cannot be moved. +/// +/// Use [`with_flags()`](Self::with_flags) with a [`PyBufferRequest`] value to acquire a view. +/// The available accessors depend on the flags used. +#[repr(transparent)] +pub struct PyUntypedBufferView< + Flag: PyBufferRequestType = RequestFlags< + false, + false, + false, + false, + false, + CONTIGUITY_UNDEFINED, + >, +> { + raw: ffi::Py_buffer, + _flags: PhantomData, + _pin: PhantomPinned, +} + +impl PyUntypedBufferView { + /// Gets the pointer to the start of the buffer memory. + /// + /// See [`PyUntypedBuffer::buf_ptr()`] for considerations about how + /// to safely read and write data through this pointer. + #[inline] + pub fn buf_ptr(&self) -> *mut c_void { + self.raw.buf + } + + /// Returns the Python object that owns the buffer data. + #[inline] + pub fn obj<'py>(&self, py: Python<'py>) -> Option<&Bound<'py, PyAny>> { + unsafe { Bound::ref_from_ptr_or_opt(py, &self.raw.obj).as_ref() } + } + + /// Gets whether the underlying buffer is read-only. + #[inline] + pub fn readonly(&self) -> bool { + !Flag::WRITABLE && self.raw.readonly != 0 + } + + /// Gets the size of a single element, in bytes. + #[inline] + pub fn item_size(&self) -> usize { + if const { Flag::IS_SIMPLE } { + 1 + } else { + self.raw.itemsize as usize + } + } + + /// Gets the total number of items. + #[inline] + pub fn item_count(&self) -> usize { + self.len_bytes() / self.item_size() + } + + /// `item_size() * item_count()`. + /// For contiguous arrays, this is the length of the underlying memory block. + #[inline] + pub fn len_bytes(&self) -> usize { + self.raw.len as usize + } + + /// Gets the number of dimensions. + /// + /// Always at least 1. Scalar buffers are rejected during acquisition. + #[inline] + pub fn dimensions(&self) -> usize { + self.raw.ndim as usize + } + + /// Gets whether the buffer is contiguous in C-style order. + #[inline] + pub fn is_c_contiguous(&self) -> bool { + Flag::CONTIGUITY == PyBufferContiguity::C + || unsafe { ffi::PyBuffer_IsContiguous(&self.raw, b'C' as core::ffi::c_char) != 0 } + } + + /// Gets whether the buffer is contiguous in Fortran-style order. + #[inline] + pub fn is_fortran_contiguous(&self) -> bool { + Flag::CONTIGUITY == PyBufferContiguity::F + || unsafe { ffi::PyBuffer_IsContiguous(&self.raw, b'F' as core::ffi::c_char) != 0 } + } +} + +impl PyUntypedBufferView { + /// A [struct module style](https://docs.python.org/3/c-api/buffer.html#c.Py_buffer.format) + /// string describing the contents of a single item. + #[inline] + pub fn format(&self) -> &CStr + where + Flag: flags::IncludesFormat, + { + if Flag::ASSUME_U8 { + return ffi::c_str!("B"); + } + + debug_assert!(!self.raw.format.is_null()); + unsafe { CStr::from_ptr(self.raw.format) } + } + + /// Attempt to interpret this untyped view as containing elements of type `T`. + pub fn as_typed(&self) -> PyResult<&PyBufferView> + where + Flag: flags::IncludesFormat, + { + self.ensure_compatible_with::()?; + // SAFETY: PyBufferView is repr(transparent) around PyUntypedBufferView<..>. + // Validation establishes its element compatibility, non-null data pointer, + // and alignment invariants before any safe slice accessor can be called. + Ok(unsafe { NonNull::from(self).cast::>().as_ref() }) + } + + fn ensure_compatible_with(&self) -> PyResult<()> + where + Flag: flags::IncludesFormat, + { + check_buffer_compatibility::(self.raw.buf, self.item_size(), self.format()) + } + + /// Returns the shape array. `shape[i]` is the length of dimension `i`. + /// + /// Despite Python using an array of signed integers, the values are guaranteed to be + /// non-negative. However, dimensions of length 0 are possible and might need special + /// attention. + #[inline] + pub fn shape(&self) -> &[usize] + where + Flag: flags::IncludesShape, + { + debug_assert!(!self.raw.shape.is_null()); + unsafe { slice::from_raw_parts(self.raw.shape.cast(), self.raw.ndim as usize) } + } + + /// Returns the strides array. + /// + /// Stride values can be any integer. For regular arrays, strides are usually positive, + /// but a consumer MUST be able to handle the case `strides[n] <= 0`. + #[inline] + pub fn strides(&self) -> &[isize] + where + Flag: flags::IncludesStrides, + { + debug_assert!(!self.raw.strides.is_null()); + unsafe { slice::from_raw_parts(self.raw.strides, self.raw.ndim as usize) } + } + + /// Returns the suboffsets array. + /// + /// May return `None` even when suboffsets were requested if the exporter sets + /// `suboffsets` to `NULL`. + #[inline] + pub fn suboffsets(&self) -> Option<&[isize]> + where + Flag: flags::IncludesSuboffsets, + { + if self.raw.suboffsets.is_null() { + return None; + } + + Some(unsafe { slice::from_raw_parts(self.raw.suboffsets, self.raw.ndim as usize) }) + } +} + +/// Check that a buffer is compatible with element type `T`. +fn check_buffer_compatibility( + buf: *mut c_void, + itemsize: usize, + format: &CStr, +) -> PyResult<()> { + let name = core::any::type_name::(); + + if mem::size_of::() != itemsize || !T::is_compatible_format(format) { + return Err(PyBufferError::new_err(format!( + "buffer contents are not compatible with {name}" + ))); + } + + let buf = + NonNull::new(buf).ok_or_else(|| PyBufferError::new_err("buffer data pointer is null"))?; + + if buf.as_ptr().align_offset(mem::align_of::()) != 0 { + return Err(PyBufferError::new_err(format!( + "buffer contents are insufficiently aligned for {name}" + ))); + } + + Ok(()) +} + +impl PyUntypedBufferView { + /// Acquire a buffer view with the given flags, + /// pass it to `f`, then release the buffer. + /// + /// Use [`PyBufferRequest::simple()`] or one of the compound-request constructors such as + /// [`PyBufferRequest::full_ro()`] to acquire a view. + /// + /// The requested flags constrain what exporters may return. For example, without shape + /// information only 1-dimensional buffers are permitted. + pub fn with_flags( + obj: &Bound<'_, PyAny>, + flags: PyBufferRequest, + f: impl FnOnce(&PyUntypedBufferView) -> R, + ) -> PyResult { + let mut view = mem::MaybeUninit::>::uninit(); + + // SAFETY: PyUntypedBufferView is repr(transparent) around ffi::Py_buffer, + // so the pointer has the layout and alignment expected by PyObject_GetBuffer. + err::error_on_negative(obj.py(), unsafe { + ffi::PyObject_GetBuffer(obj.as_ptr(), view.as_mut_ptr().cast(), flags.0) + })?; + + // SAFETY: PyObject_GetBuffer initialized the raw buffer successfully, and + // the zero-sized marker fields need no initialization. Borrow the view + // without moving it, since the exporter may have made it self-referential. + let guard = BufferViewGuard(unsafe { view.assume_init_mut() }); + + if guard.0.raw.ndim == 0 { + return Err(PyBufferError::new_err("scalar buffers are not supported")); + } + + Ok(f(guard.0)) + } +} + +/// Drops an initialized view in place, including when the callback panics. +/// The view's storage is a MaybeUninit, so it will not be dropped a second time. +struct BufferViewGuard<'a, Flag: PyBufferRequestType>(&'a mut PyUntypedBufferView); + +impl Drop for BufferViewGuard<'_, Flag> { + fn drop(&mut self) { + // SAFETY: The view is initialized and exclusively borrowed by this guard. + // Its MaybeUninit storage will not be accessed again after this drop. + unsafe { ptr::drop_in_place(self.0) } + } +} + +impl Drop for PyUntypedBufferView { + fn drop(&mut self) { + // Avoid creating a mutable reference to the Unpin raw buffer, which would + // invalidate self-references the exporter may still use during release. + unsafe { ffi::PyBuffer_Release(&raw mut self.raw) } + } +} + +impl Debug for PyUntypedBufferView { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + debug_buffer("PyUntypedBufferView", &self.raw, f) + } +} + +impl PyBufferView { + /// Acquire a typed buffer view with `PyBufferRequest::full_ro()` flags, + /// validating that the buffer format is compatible with `T`. + pub fn with(obj: &Bound<'_, PyAny>, f: impl FnOnce(&PyBufferView) -> R) -> PyResult { + PyUntypedBufferView::with_flags(obj, PyBufferRequest::full_ro(), |view| { + view.as_typed::().map(f) + })? + } + + /// Acquire a typed buffer view with the given flags. + /// + /// The flags are passed to the exporter as requested. Format information must be + /// available for type validation: [`PyBufferRequest::simple()`] and its writable + /// variant imply unsigned bytes, while other requests must include [`.format()`](PyBufferRequest::format). + pub fn with_flags( + obj: &Bound<'_, PyAny>, + flags: PyBufferRequest, + f: impl FnOnce(&PyBufferView) -> R, + ) -> PyResult + where + Flag: PyBufferRequestType + flags::IncludesFormat, + { + PyUntypedBufferView::with_flags(obj, flags, |view| view.as_typed::().map(f))? + } +} + +impl PyBufferView { + /// Gets the buffer memory as a slice. + /// + /// Returns `None` if the buffer is not C-contiguous. + /// + /// Accessing the memory requires upholding the [buffer memory access + /// requirements](PyUntypedBufferView::buf_ptr). + pub fn as_slice_ptr(&self) -> Option> { + self.is_c_contiguous() + .then(|| self.slice_pointer_internal()) + } + + /// Gets the buffer memory as a slice. + /// + /// Returns `None` if the buffer is not Fortran-contiguous. + /// + /// Accessing the memory requires upholding the [buffer memory access + /// requirements](PyUntypedBufferView::buf_ptr). + pub fn as_fortran_slice_ptr(&self) -> Option> { + self.is_fortran_contiguous() + .then(|| self.slice_pointer_internal()) + } + + /// Gets the buffer memory as a slice. The buffer is guaranteed C-contiguous. + /// + /// Accessing the memory requires upholding the [buffer memory access + /// requirements](PyUntypedBufferView::buf_ptr). + pub fn as_contiguous_slice_ptr(&self) -> NonNull<[T]> + where + Flag: flags::GuaranteesCContiguous, + { + self.slice_pointer_internal() + } + + /// Gets the buffer memory as a slice pointer. The buffer is guaranteed Fortran-contiguous. + /// + /// Accessing the memory requires upholding the [buffer memory access + /// requirements](PyUntypedBufferView::buf_ptr). + pub fn as_fortran_contiguous_slice_ptr(&self) -> NonNull<[T]> + where + Flag: flags::GuaranteesFContiguous, + { + self.slice_pointer_internal() + } + + fn slice_pointer_internal(&self) -> NonNull<[T]> { + NonNull::new(ptr::slice_from_raw_parts_mut( + self.0.raw.buf.cast(), + self.item_count(), + )) + .expect("buffer data pointer is non-null") + } +} + +impl core::ops::Deref for PyBufferView { + type Target = PyUntypedBufferView; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Debug for PyBufferView { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + debug_buffer("PyBufferView", &self.0.raw, f) + } +} + #[cfg(test)] mod tests { use super::*; @@ -877,8 +1406,8 @@ mod tests { concat!( "PyBuffer {{ buf: {:?}, obj: {:?}, ", "len: 5, itemsize: 1, readonly: 1, ", - "ndim: 1, format: \"B\", shape: [5], ", - "strides: [1], suboffsets: None, internal: {:?} }}", + "ndim: 1, format: Some(\"B\"), shape: Some([5]), ", + "strides: Some([1]), suboffsets: None, internal: {:?} }}", ), buffer.raw().buf, buffer.raw().obj, @@ -886,6 +1415,21 @@ mod tests { ); let debug_repr = format!("{:?}", buffer); assert_eq!(debug_repr, expected); + + let untyped = PyUntypedBuffer::get(&bytes).unwrap(); + let expected = format!( + concat!( + "PyUntypedBuffer {{ buf: {:?}, obj: {:?}, ", + "len: 5, itemsize: 1, readonly: 1, ", + "ndim: 1, format: Some(\"B\"), shape: Some([5]), ", + "strides: Some([1]), suboffsets: None, internal: {:?} }}", + ), + untyped.raw().buf, + untyped.raw().obj, + untyped.raw().internal + ); + let debug_repr = format!("{:?}", untyped); + assert_eq!(debug_repr, expected); }); } @@ -1003,6 +1547,27 @@ mod tests { (c"=d", Float { bytes: 8 }), (c"=z", Unknown), (c"=0", Unknown), + // bare char (no prefix) goes to native_element_type_from_type_char + ( + c"b", + SignedInteger { + bytes: size_of::(), + }, + ), + ( + c"B", + UnsignedInteger { + bytes: size_of::(), + }, + ), + (c"?", Bool), + (c"f", Float { bytes: 4 }), + (c"d", Float { bytes: 8 }), + (c"z", Unknown), + // <, >, ! prefixes go to standard_element_type_from_type_char + (c"H", UnsignedInteger { bytes: 2 }), + (c"!q", SignedInteger { bytes: 8 }), // unknown prefix -> Unknown (c":b", Unknown), ] { @@ -1041,6 +1606,7 @@ mod tests { assert_eq!(slice.len(), 5); assert_eq!(slice[0].get(), b'a'); assert_eq!(slice[2].get(), b'c'); + assert_eq!(unsafe { *slice[0].as_ptr() }, b'a'); assert_eq!(unsafe { *(buffer.get_ptr(&[1]).cast::()) }, b'b'); @@ -1115,24 +1681,6 @@ mod tests { }); } - #[test] - fn test_untyped_buffer() { - Python::attach(|py| { - let bytes = PyBytes::new(py, b"abcde"); - let untyped = PyUntypedBuffer::get(&bytes).unwrap(); - assert_eq!(untyped.dimensions(), 1); - assert_eq!(untyped.item_count(), 5); - assert_eq!(untyped.format().to_str().unwrap(), "B"); - assert_eq!(untyped.shape(), [5]); - - let typed: &PyBuffer = untyped.as_typed().unwrap(); - assert_eq!(typed.dimensions(), 1); - assert_eq!(typed.item_count(), 5); - assert_eq!(typed.format().to_str().unwrap(), "B"); - assert_eq!(typed.shape(), [5]); - }); - } - #[test] fn test_obj_getter() { Python::attach(|py| { @@ -1179,4 +1727,699 @@ mod tests { assert!(buffer.as_fortran_slice_ptr().is_none()); }); } + + #[test] + fn test_copy_to_fortran_slice() { + Python::attach(|py| { + let array = py + .import("array") + .unwrap() + .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None) + .unwrap(); + let buffer = PyBuffer::get(&array).unwrap(); + + // wrong length + assert!(buffer.copy_to_fortran_slice(py, &mut [0.0f32]).is_err()); + // correct length + let mut arr = [0.0f32; 4]; + buffer.copy_to_fortran_slice(py, &mut arr).unwrap(); + assert_eq!(arr, [1.0, 1.5, 2.0, 2.5]); + }); + } + + #[test] + fn test_copy_from_slice_wrong_length() { + Python::attach(|py| { + let array = py + .import("array") + .unwrap() + .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None) + .unwrap(); + let buffer = PyBuffer::get(&array).unwrap(); + // writable buffer, but wrong length + assert!(!buffer.readonly()); + assert!(buffer.copy_from_slice(py, &[0.0f32; 2]).is_err()); + assert!(buffer.copy_from_fortran_slice(py, &[0.0f32; 2]).is_err()); + }); + } + + #[test] + fn test_untyped_buffer() { + Python::attach(|py| { + let bytes = PyBytes::new(py, b"abcde"); + let buffer = PyUntypedBuffer::get(&bytes).unwrap(); + assert_eq!(buffer.dimensions(), 1); + assert_eq!(buffer.item_count(), 5); + assert_eq!(buffer.format().to_str().unwrap(), "B"); + assert_eq!(buffer.shape(), [5]); + assert!(!buffer.buf_ptr().is_null()); + assert_eq!(buffer.strides(), &[1]); + assert_eq!(buffer.len_bytes(), 5); + assert_eq!(buffer.item_size(), 1); + assert!(buffer.readonly()); + assert!(buffer.suboffsets().is_none()); + + assert!(format!("{:?}", buffer).starts_with("PyUntypedBuffer { buf: ")); + + let typed: &PyBuffer = buffer.as_typed().unwrap(); + assert_eq!(typed.dimensions(), 1); + assert_eq!(typed.item_count(), 5); + assert_eq!(typed.format().to_str().unwrap(), "B"); + assert_eq!(typed.shape(), [5]); + }); + } + + #[test] + fn test_untyped_buffer_view() { + Python::attach(|py| { + let bytes = PyBytes::new(py, b"abcde"); + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::full_ro(), |view| { + assert!(!view.buf_ptr().is_null()); + assert_eq!(view.len_bytes(), 5); + assert_eq!(view.item_size(), 1); + assert_eq!(view.item_count(), 5); + assert!(view.readonly()); + assert_eq!(view.dimensions(), 1); + // with_flags() uses PyBufferRequest::full_ro() — all Known, direct return types + assert_eq!(view.format().to_str().unwrap(), "B"); + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + assert!(view.suboffsets().is_none()); + assert!(view.is_c_contiguous()); + assert!(view.is_fortran_contiguous()); + assert!(view.obj(py).unwrap().is(&bytes)); + }) + .unwrap(); + }); + } + + #[test] + fn test_typed_buffer_view() { + Python::attach(|py| { + let bytes = PyBytes::new(py, b"abcde"); + PyBufferView::::with(&bytes, |view| { + assert_eq!(view.dimensions(), 1); + assert_eq!(view.item_count(), 5); + // PyBufferView::with uses PyBufferRequest::full_ro() — all Known + assert_eq!(view.format().to_str().unwrap(), "B"); + assert_eq!(view.shape(), [5]); + assert!(view.suboffsets().is_none()); + + let slice_ptr = view.as_slice_ptr().unwrap(); + assert_eq!(slice_ptr.len(), 5); + // SAFETY: bytes are immutable and the export is still alive. + assert_eq!(unsafe { slice_ptr.as_ref() }, b"abcde"); + assert!(view.readonly()); + + let fortran_slice_ptr = view.as_fortran_slice_ptr().unwrap(); + assert_eq!(fortran_slice_ptr, slice_ptr); + }) + .unwrap(); + }); + } + + #[test] + fn test_buffer_view_array() { + Python::attach(|py| { + let array = py + .import("array") + .unwrap() + .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None) + .unwrap(); + PyBufferView::::with(&array, |view| { + assert_eq!(view.dimensions(), 1); + assert_eq!(view.item_count(), 4); + assert_eq!(view.format().to_str().unwrap(), "f"); + assert_eq!(view.shape(), [4]); + + let mut slice_ptr = view.as_slice_ptr().unwrap(); + assert_eq!(slice_ptr.len(), 4); + assert!(!view.readonly()); + // SAFETY: the array is writable, not shared, and the export is still alive. + let slice = unsafe { slice_ptr.as_mut() }; + assert_eq!(slice, &[1.0, 1.5, 2.0, 2.5]); + slice[3] = 2.75; + assert_eq!(slice[3], 2.75); + }) + .unwrap(); + }); + } + + #[test] + fn test_buffer_view_with_flags() { + Python::attach(|py| { + let bytes = PyBytes::new(py, b"abcde"); + + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::simple(), |view| { + assert_eq!(view.item_count(), 5); + assert_eq!(view.len_bytes(), 5); + assert!(view.readonly()); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::simple().nd(), |view| { + assert_eq!(view.item_count(), 5); + assert_eq!(view.shape(), [5]); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::simple().strides(), |view| { + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::simple().indirect(), |view| { + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + assert!(view.suboffsets().is_none()); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::simple().format(), |view| { + assert_eq!(view.item_count(), 5); + assert_eq!(view.format().to_str().unwrap(), "B"); + }) + .unwrap(); + }); + } + + #[test] + fn test_simple_buffer_view_array() { + Python::attach(|py| { + let array = py + .import("array") + .unwrap() + .call_method("array", ("I", (1, 2, 3)), None) + .unwrap(); + let byte_count = 3 * mem::size_of::(); + + PyUntypedBufferView::with_flags(&array, PyBufferRequest::simple(), |view| { + assert_eq!(view.format(), c"B"); + assert_eq!(view.item_size(), 1); + assert_eq!(view.item_count(), byte_count); + assert_eq!( + view.as_typed::().unwrap().as_slice_ptr().unwrap().len(), + byte_count + ); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&array, PyBufferRequest::simple().writable(), |view| { + assert_eq!(view.item_size(), 1); + assert_eq!(view.item_count(), byte_count); + assert_eq!( + view.as_typed::().unwrap().as_slice_ptr().unwrap().len(), + byte_count + ); + }) + .unwrap(); + + let memoryview = py + .import("builtins") + .unwrap() + .call_method1("memoryview", (&array,)) + .unwrap(); + PyBufferView::::with_flags(&memoryview, PyBufferRequest::simple(), |view| { + assert!(view.0.raw.format.is_null()); + assert!(view.0.raw.shape.is_null()); + assert_eq!(view.as_slice_ptr().unwrap().len(), byte_count); + }) + .unwrap(); + }); + } + + #[test] + fn test_buffer_view_rejects_scalar() { + Python::attach(|py| { + let scalar = py + .eval(c"memoryview(b'x').cast('B', shape=[])", None, None) + .unwrap(); + + let err = PyBufferView::::with(&scalar, |_| panic!("scalar buffer")).unwrap_err(); + assert_eq!( + err.to_string(), + "BufferError: scalar buffers are not supported" + ); + + // Rejection must release the export. + scalar.call_method0("release").unwrap(); + }); + } + + #[test] + #[cfg(not(any(PyPy, target_os = "wasi")))] + fn test_buffer_view_rejects_null_data() { + Python::attach(|py| { + let empty = py + .eval( + c"memoryview((__import__('ctypes').c_ubyte * 0).from_address(0)).cast('B')", + None, + None, + ) + .unwrap(); + + let err = + PyBufferView::::with(&empty, |_| panic!("null data pointer")).unwrap_err(); + assert_eq!(err.to_string(), "BufferError: buffer data pointer is null"); + + let empty_bytes = PyBytes::new(py, b""); + PyBufferView::::with(&empty_bytes, |view| { + assert_eq!(view.as_slice_ptr().unwrap().len(), 0); + }) + .unwrap(); + }); + } + + #[test] + fn test_typed_buffer_view_with_flags() { + Python::attach(|py| { + let array = py + .import("array") + .unwrap() + .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None) + .unwrap(); + let view = py + .import("builtins") + .unwrap() + .call_method1("memoryview", (&array,)) + .unwrap(); + + PyUntypedBufferView::with_flags( + &view, + PyBufferRequest::simple().nd().format(), + |view| { + assert_eq!(view.format(), c"f"); + assert_eq!(view.shape(), [4]); + }, + ) + .unwrap(); + + PyBufferView::::with_flags( + &view, + PyBufferRequest::simple().nd().format(), + |view| { + assert_eq!(view.item_count(), 4); + assert_eq!(view.format().to_str().unwrap(), "f"); + assert_eq!(view.shape(), [4]); + + let mut slice_ptr = view.as_slice_ptr().unwrap(); + assert!(!view.readonly()); + // SAFETY: the array is writable, not shared, and the export is still alive. + let slice = unsafe { slice_ptr.as_mut() }; + assert_eq!(slice[0], 1.0); + assert_eq!(slice[3], 2.5); + slice[0] = 9.0; + assert_eq!(slice[0], 9.0); + }, + ) + .unwrap(); + }); + } + + #[test] + fn test_typed_buffer_view_with_flags_incompatible() { + Python::attach(|py| { + let bytes = PyBytes::new(py, b"abcde"); + let result = PyBufferView::::with_flags( + &bytes, + PyBufferRequest::simple().nd().format(), + |_view| {}, + ); + assert!(result.is_err()); + }); + } + + #[test] + fn test_c_contiguous_slice_ptr() { + Python::attach(|py| { + let array = py + .import("array") + .unwrap() + .call_method("array", ("f", (1.0, 1.5, 2.0)), None) + .unwrap(); + + PyBufferView::::with_flags( + &array, + PyBufferRequest::simple().c_contiguous().format(), + |view| { + let slice_ptr = view.as_contiguous_slice_ptr(); + assert_eq!(slice_ptr.len(), 3); + assert_eq!(Some(slice_ptr), view.as_slice_ptr()); + // SAFETY: the array is not shared and the export is still alive. + assert_eq!(unsafe { slice_ptr.as_ref() }, &[1.0, 1.5, 2.0]); + }, + ) + .unwrap(); + + PyBufferView::::with(&array, |view| { + let mut slice_ptr = view.as_slice_ptr().unwrap(); + assert!(!view.readonly()); + // SAFETY: the array is writable, not shared, and the export is still alive. + let slice = unsafe { slice_ptr.as_mut() }; + slice[2] = 9.0; + assert_eq!(slice[2], 9.0); + }) + .unwrap(); + }); + } + + #[test] + fn test_buffer_view_error() { + Python::attach(|py| { + let list = crate::types::PyList::empty(py); + let result = + PyUntypedBufferView::with_flags(&list, PyBufferRequest::full_ro(), |_view| {}); + assert!(result.is_err()); + }); + } + + #[test] + fn test_flag_builders() { + fn assert_contiguity( + _: PyBufferRequest, + expected: PyBufferContiguity, + ) { + assert_eq!(Flag::CONTIGUITY, expected); + } + + fn assert_direct< + const FORMAT: bool, + const SHAPE: bool, + const STRIDE: bool, + const WRITABLE: bool, + const CONTIGUITY: u8, + >( + _: PyBufferRequest>, + ) { + } + + fn assert_indirect< + const FORMAT: bool, + const SHAPE: bool, + const STRIDE: bool, + const WRITABLE: bool, + const CONTIGUITY: u8, + >( + _: PyBufferRequest>, + ) { + } + + assert_direct(PyBufferRequest::simple()); + assert_direct(PyBufferRequest::records_ro()); + assert_direct(PyBufferRequest::strided_ro()); + assert_direct(PyBufferRequest::contig_ro()); + assert_indirect(PyBufferRequest::simple().indirect()); + assert_indirect(PyBufferRequest::full_ro()); + assert_indirect(PyBufferRequest::full()); + assert_direct(PyBufferRequest::full_ro().c_contiguous()); + assert_direct(PyBufferRequest::full().c_contiguous()); + + assert_contiguity(PyBufferRequest::simple(), PyBufferContiguity::Undefined); + assert_contiguity( + PyBufferRequest::simple().c_contiguous(), + PyBufferContiguity::C, + ); + assert_contiguity( + PyBufferRequest::simple().f_contiguous(), + PyBufferContiguity::F, + ); + assert_contiguity( + PyBufferRequest::simple().any_contiguous(), + PyBufferContiguity::Any, + ); + + Python::attach(|py| { + let bytes = PyBytes::new(py, b"abcde"); + let array = py + .import("array") + .unwrap() + .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None) + .unwrap(); + + // Primitive builders + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::simple(), |view| { + assert_eq!(view.format().to_str().unwrap(), "B"); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::simple().format(), |view| { + assert_eq!(view.format().to_str().unwrap(), "B"); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::simple().nd(), |view| { + assert_eq!(view.shape(), [5]); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::simple().strides(), |view| { + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::simple().indirect(), |view| { + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + assert!(view.suboffsets().is_none()); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&array, PyBufferRequest::simple().writable(), |view| { + assert_eq!(view.format().to_str().unwrap(), "B"); + assert!(!view.readonly()); + }) + .unwrap(); + + PyUntypedBufferView::with_flags( + &array, + PyBufferRequest::simple().writable().nd(), + |view| { + assert_eq!(view.shape(), [4]); + assert!(!view.readonly()); + }, + ) + .unwrap(); + + // Chained primitive builders + PyUntypedBufferView::with_flags( + &bytes, + PyBufferRequest::simple().nd().format(), + |view| { + assert_eq!(view.shape(), [5]); + assert_eq!(view.format().to_str().unwrap(), "B"); + }, + ) + .unwrap(); + + PyUntypedBufferView::with_flags( + &bytes, + PyBufferRequest::simple().strides().format(), + |view| { + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + assert_eq!(view.format().to_str().unwrap(), "B"); + }, + ) + .unwrap(); + + // Contiguity builders + PyUntypedBufferView::with_flags( + &bytes, + PyBufferRequest::simple().c_contiguous(), + |view| { + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + }, + ) + .unwrap(); + + PyUntypedBufferView::with_flags( + &bytes, + PyBufferRequest::simple().f_contiguous(), + |view| { + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + }, + ) + .unwrap(); + + PyUntypedBufferView::with_flags( + &bytes, + PyBufferRequest::simple().any_contiguous(), + |view| { + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + }, + ) + .unwrap(); + + // Compound requests (read-only) + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::full_ro(), |view| { + assert_eq!(view.format().to_str().unwrap(), "B"); + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + assert!(view.suboffsets().is_none()); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::records_ro(), |view| { + assert_eq!(view.format().to_str().unwrap(), "B"); + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::strided_ro(), |view| { + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::contig_ro(), |view| { + assert_eq!(view.shape(), [5]); + assert!(view.is_c_contiguous()); + }) + .unwrap(); + + // Writable compound requests + PyUntypedBufferView::with_flags(&array, PyBufferRequest::full(), |view| { + assert_eq!(view.format().to_str().unwrap(), "f"); + assert_eq!(view.shape(), [4]); + assert_eq!(view.strides(), [4]); + assert!(view.suboffsets().is_none()); + assert!(!view.readonly()); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&array, PyBufferRequest::records(), |view| { + assert_eq!(view.format().to_str().unwrap(), "f"); + assert_eq!(view.shape(), [4]); + assert!(!view.readonly()); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&array, PyBufferRequest::strided(), |view| { + assert_eq!(view.shape(), [4]); + assert_eq!(view.strides(), [4]); + assert!(!view.readonly()); + }) + .unwrap(); + + PyUntypedBufferView::with_flags(&array, PyBufferRequest::contig(), |view| { + assert_eq!(view.shape(), [4]); + assert!(!view.readonly()); + assert!(view.is_c_contiguous()); + }) + .unwrap(); + + // Compound + contiguity + PyUntypedBufferView::with_flags( + &bytes, + PyBufferRequest::full_ro().c_contiguous(), + |view| { + assert_eq!(view.format().to_str().unwrap(), "B"); + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + }, + ) + .unwrap(); + + PyUntypedBufferView::with_flags( + &array, + PyBufferRequest::full().c_contiguous(), + |view| { + assert_eq!(view.format().to_str().unwrap(), "f"); + assert!(!view.readonly()); + }, + ) + .unwrap(); + + PyUntypedBufferView::with_flags( + &bytes, + PyBufferRequest::strided_ro().format(), + |view| { + assert_eq!(view.format().to_str().unwrap(), "B"); + assert_eq!(view.shape(), [5]); + assert_eq!(view.strides(), [1]); + }, + ) + .unwrap(); + + PyUntypedBufferView::with_flags( + &bytes, + PyBufferRequest::simple().c_contiguous().format(), + |view| { + assert_eq!(view.format().to_str().unwrap(), "B"); + assert_eq!(view.shape(), [5]); + }, + ) + .unwrap(); + + // Contiguity builder on typed view + PyBufferView::::with_flags(&bytes, PyBufferRequest::simple().format(), |view| { + assert_eq!(view.format().to_str().unwrap(), "B"); + assert_eq!(view.item_count(), 5); + }) + .unwrap(); + + PyBufferView::::with_flags(&array, PyBufferRequest::contig().format(), |view| { + let mut slice_ptr = view.as_contiguous_slice_ptr(); + assert!(!view.readonly()); + // SAFETY: the array is writable, not shared, and the export is still alive. + let slice = unsafe { slice_ptr.as_mut() }; + assert_eq!(slice[0], 1.0); + slice[0] = 9.0; + assert_eq!(slice[0], 9.0); + }) + .unwrap(); + + // SIMPLE format() returns "B" + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::simple(), |view| { + assert_eq!(view.format().to_str().unwrap(), "B"); + }) + .unwrap(); + }); + } + + #[test] + fn test_buffer_view_debug() { + Python::attach(|py| { + let bytes = PyBytes::new(py, b"abcde"); + + // Debug always uses raw_format/raw_shape/raw_strides (Option in output) + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::full_ro(), |view| { + let expected = format!( + concat!( + "PyUntypedBufferView {{ buf: {:?}, obj: {:?}, ", + "len: 5, itemsize: 1, readonly: 1, ", + "ndim: 1, format: Some(\"B\"), shape: Some([5]), ", + "strides: Some([1]), suboffsets: None, internal: {:?} }}", + ), + view.raw.buf, view.raw.obj, view.raw.internal, + ); + + let debug_repr = format!("{:?}", view); + assert_eq!(debug_repr, expected); + }) + .unwrap(); + + PyBufferView::::with(&bytes, |view| { + let expected = format!( + concat!( + "PyBufferView {{ buf: {:?}, obj: {:?}, ", + "len: 5, itemsize: 1, readonly: 1, ", + "ndim: 1, format: Some(\"B\"), shape: Some([5]), ", + "strides: Some([1]), suboffsets: None, internal: {:?} }}", + ), + view.0.raw.buf, view.0.raw.obj, view.0.raw.internal, + ); + + let debug_repr = format!("{:?}", view); + assert_eq!(debug_repr, expected); + }) + .unwrap(); + }); + } } diff --git a/src/buffer/flags.rs b/src/buffer/flags.rs new file mode 100644 index 00000000000..a05fe00ff2a --- /dev/null +++ b/src/buffer/flags.rs @@ -0,0 +1,298 @@ +use super::PyBufferContiguity; + +pub(super) const CONTIGUITY_UNDEFINED: u8 = PyBufferContiguity::Undefined as u8; +pub(super) const CONTIGUITY_C: u8 = PyBufferContiguity::C as u8; +pub(super) const CONTIGUITY_F: u8 = PyBufferContiguity::F as u8; +pub(super) const CONTIGUITY_ANY: u8 = PyBufferContiguity::Any as u8; + +pub struct PyBufferFlags< + const FORMAT: bool, + const SHAPE: bool, + const STRIDE: bool, + const INDIRECT: bool, + const WRITABLE: bool, + const CONTIGUITY: u8, +>; + +#[diagnostic::on_unimplemented( + message = "format information has already been requested for this buffer request", + note = "remove the extra `.format()` call" +)] +pub trait CanRequestFormat {} +#[diagnostic::do_not_recommend] +impl< + const SHAPE: bool, + const STRIDE: bool, + const INDIRECT: bool, + const WRITABLE: bool, + const CONTIGUITY: u8, +> CanRequestFormat for PyBufferFlags +{ +} + +#[diagnostic::on_unimplemented( + message = "shape information has already been requested for this buffer request", + note = "remove the extra `.nd()` call" +)] +pub trait CanRequestShape {} +#[diagnostic::do_not_recommend] +impl< + const FORMAT: bool, + const STRIDE: bool, + const INDIRECT: bool, + const WRITABLE: bool, + const CONTIGUITY: u8, +> CanRequestShape for PyBufferFlags +{ +} + +#[diagnostic::on_unimplemented( + message = "stride information has already been requested for this buffer request", + note = "remove the extra `.strides()` call" +)] +pub trait CanRequestStrides {} +#[diagnostic::do_not_recommend] +impl< + const FORMAT: bool, + const SHAPE: bool, + const INDIRECT: bool, + const WRITABLE: bool, + const CONTIGUITY: u8, +> CanRequestStrides for PyBufferFlags +{ +} + +#[diagnostic::on_unimplemented( + message = "suboffsets can only be requested on a direct unconstrained buffer request", + note = "call `.indirect()` before any contiguity builder, and only once" +)] +pub trait CanRequestIndirect {} +#[diagnostic::do_not_recommend] +impl + CanRequestIndirect + for PyBufferFlags +{ +} + +#[diagnostic::on_unimplemented( + message = "writability has already been requested for this buffer request", + note = "remove the extra `.writable()` call" +)] +pub trait CanRequestWritable {} +#[diagnostic::do_not_recommend] +impl< + const FORMAT: bool, + const SHAPE: bool, + const STRIDE: bool, + const INDIRECT: bool, + const CONTIGUITY: u8, +> CanRequestWritable for PyBufferFlags +{ +} + +#[diagnostic::on_unimplemented( + message = "contiguity has already been constrained for this buffer request", + note = "only one of `.c_contiguous()`, `.f_contiguous()`, or `.any_contiguous()` may be used" +)] +pub trait CanRequestContiguity {} +#[diagnostic::do_not_recommend] +impl< + const FORMAT: bool, + const SHAPE: bool, + const STRIDE: bool, + const INDIRECT: bool, + const WRITABLE: bool, +> CanRequestContiguity + for PyBufferFlags +{ +} + +#[diagnostic::on_unimplemented( + message = "C contiguity is not guaranteed by the requested buffer flags", + note = "use `.c_contiguous()` when building a buffer request to guarantee C contiguity" +)] +pub trait GuaranteesCContiguous {} +#[diagnostic::do_not_recommend] +impl< + const FORMAT: bool, + const SHAPE: bool, + const STRIDE: bool, + const INDIRECT: bool, + const WRITABLE: bool, +> GuaranteesCContiguous + for PyBufferFlags +{ +} + +#[diagnostic::on_unimplemented( + message = "Fortran contiguity is not guaranteed by the requested buffer flags", + note = "use `.f_contiguous()` when building a buffer request to guarantee Fortran contiguity" +)] +pub trait GuaranteesFContiguous {} +#[diagnostic::do_not_recommend] +impl< + const FORMAT: bool, + const SHAPE: bool, + const STRIDE: bool, + const INDIRECT: bool, + const WRITABLE: bool, +> GuaranteesFContiguous + for PyBufferFlags +{ +} + +/// Marker trait for buffer flags which have requested format information. +#[diagnostic::on_unimplemented( + message = "format information is not available with the requested buffer flags", + note = "use `.format()` when building a buffer request to request format information", + note = "`PyBufferRequest::simple()` and `PyBufferRequest::simple().writable()` also imply u8 format" +)] +pub trait IncludesFormat { + const ASSUME_U8: bool; +} + +#[diagnostic::do_not_recommend] +impl< + const SHAPE: bool, + const STRIDE: bool, + const INDIRECT: bool, + const WRITABLE: bool, + const CONTIGUITY: u8, +> IncludesFormat for PyBufferFlags +{ + const ASSUME_U8: bool = false; +} + +// Simple (maybe writable) buffers also have an implied u8 format. +#[diagnostic::do_not_recommend] +impl IncludesFormat + for PyBufferFlags +{ + const ASSUME_U8: bool = true; +} + +#[diagnostic::on_unimplemented( + message = "shape information is not available with the requested buffer flags", + note = "use `.nd()` when building a buffer request to request shape information" +)] +pub trait IncludesShape {} +#[diagnostic::do_not_recommend] +impl< + const FORMAT: bool, + const STRIDE: bool, + const INDIRECT: bool, + const WRITABLE: bool, + const CONTIGUITY: u8, +> IncludesShape for PyBufferFlags +{ +} + +#[diagnostic::on_unimplemented( + message = "strides information is not available with the requested buffer flags", + note = "use `.strides()` when building a buffer request to request stride information" +)] +pub trait IncludesStrides {} +#[diagnostic::do_not_recommend] +impl< + const FORMAT: bool, + const SHAPE: bool, + const INDIRECT: bool, + const WRITABLE: bool, + const CONTIGUITY: u8, +> IncludesStrides for PyBufferFlags +{ +} + +#[diagnostic::on_unimplemented( + message = "suboffsets information is not available with the requested buffer flags", + note = "use `.indirect()` when building a buffer request to request suboffset information" +)] +pub trait IncludesSuboffsets {} +#[diagnostic::do_not_recommend] +impl< + const FORMAT: bool, + const SHAPE: bool, + const STRIDE: bool, + const WRITABLE: bool, + const CONTIGUITY: u8, +> IncludesSuboffsets for PyBufferFlags +{ +} + +pub trait Sealed {} +impl< + const FORMAT: bool, + const SHAPE: bool, + const STRIDE: bool, + const INDIRECT: bool, + const WRITABLE: bool, + const CONTIGUITY: u8, +> Sealed for PyBufferFlags +{ +} + +/// Trait implemented by all hidden [`super::PyBufferRequest`] states. +pub trait PyBufferRequestType: Sealed { + /// Whether this is a simple request, which treats the buffer as unsigned bytes. + const IS_SIMPLE: bool; + + /// The contiguity requirement encoded by these flags. + const CONTIGUITY: PyBufferContiguity; + + /// Whether these flags require a writable buffer. + const WRITABLE: bool; + + /// The state after requesting format information. + type WithFormat: PyBufferRequestType + IncludesFormat; + + /// The state after requesting shape information. + type WithShape: PyBufferRequestType + IncludesShape; + + /// The state after requesting strides information. + type WithStrides: PyBufferRequestType + IncludesShape + IncludesStrides; + + /// The state after requesting indirect / suboffset information. + type WithIndirect: PyBufferRequestType + IncludesShape + IncludesStrides + IncludesSuboffsets; + + /// The state after requesting writability. + type WithWritable: PyBufferRequestType; + + /// The state after requesting C contiguity. + type WithCContiguous: PyBufferRequestType; + + /// The state after requesting Fortran contiguity. + type WithFContiguous: PyBufferRequestType; + + /// The state after requesting either C or Fortran contiguity. + type WithAnyContiguous: PyBufferRequestType; +} + +impl< + const FORMAT: bool, + const SHAPE: bool, + const STRIDE: bool, + const INDIRECT: bool, + const WRITABLE: bool, + const CONTIGUITY_REQ: u8, +> PyBufferRequestType for PyBufferFlags +{ + const IS_SIMPLE: bool = !FORMAT && !SHAPE; + + const CONTIGUITY: PyBufferContiguity = match CONTIGUITY_REQ { + CONTIGUITY_UNDEFINED => PyBufferContiguity::Undefined, + CONTIGUITY_C => PyBufferContiguity::C, + CONTIGUITY_F => PyBufferContiguity::F, + CONTIGUITY_ANY => PyBufferContiguity::Any, + _ => panic!("invalid buffer contiguity"), + }; + const WRITABLE: bool = WRITABLE; + + type WithFormat = PyBufferFlags; + type WithShape = PyBufferFlags; + type WithStrides = PyBufferFlags; + type WithIndirect = PyBufferFlags; + type WithWritable = PyBufferFlags; + type WithCContiguous = PyBufferFlags; + type WithFContiguous = PyBufferFlags; + type WithAnyContiguous = PyBufferFlags; +} diff --git a/tests/test_buffer_protocol.rs b/tests/test_buffer_protocol.rs index 00720dc25f3..f5a34c80c1b 100644 --- a/tests/test_buffer_protocol.rs +++ b/tests/test_buffer_protocol.rs @@ -3,7 +3,7 @@ #![cfg(feature = "macros")] #![cfg(any(not(Py_LIMITED_API), Py_3_11))] -use pyo3::buffer::PyBuffer; +use pyo3::buffer::{PyBuffer, PyBufferRequest, PyBufferView, PyUntypedBufferView}; use pyo3::exceptions::PyBufferError; use pyo3::ffi; use pyo3::prelude::*; @@ -137,6 +137,111 @@ fn test_releasebuffer_unraisable_error() { }); } +#[pyclass] +#[derive(Default)] +struct SelfReferentialBuffer { + releases: usize, + moved_on_release: bool, +} + +#[pymethods] +impl SelfReferentialBuffer { + unsafe fn __getbuffer__( + slf: Bound<'_, Self>, + view: *mut ffi::Py_buffer, + flags: c_int, + ) -> PyResult<()> { + // This helper sets shape and strides to point into the Py_buffer itself. + unsafe { fill_view_from_readonly_data(view, flags, b"abc", slf.into_any()) }?; + // Remember the address passed to __getbuffer__ for comparison on release. + unsafe { (*view).internal = view.cast() }; + Ok(()) + } + + unsafe fn __releasebuffer__(&mut self, view: *mut ffi::Py_buffer) { + self.releases += 1; + self.moved_on_release |= unsafe { (*view).internal != view.cast() }; + if !unsafe { (*view).format.is_null() } { + drop(unsafe { CString::from_raw((*view).format) }); + } + } +} + +fn assert_view_released(instance: &Bound<'_, SelfReferentialBuffer>, releases: usize) { + let exporter = instance.borrow(); + assert_eq!(exporter.releases, releases); + assert!(!exporter.moved_on_release); + drop(exporter); + // SAFETY: The bound instance is a live Python object on this attached thread. + assert_eq!(unsafe { ffi::Py_REFCNT(instance.as_ptr()) }, 1); +} + +#[test] +fn test_buffer_views_keep_export_in_place() { + Python::attach(|py| { + let instance = Bound::new(py, SelfReferentialBuffer::default()).unwrap(); + + PyUntypedBufferView::with_flags(&instance, PyBufferRequest::full_ro(), |view| { + assert_eq!(view.shape(), [3]); + assert_eq!(view.strides(), [1]); + }) + .unwrap(); + assert_view_released(&instance, 1); + + PyBufferView::::with(&instance, |_| {}).unwrap(); + assert_view_released(&instance, 2); + + PyBufferView::::with_flags( + &instance, + PyBufferRequest::simple().strides().format(), + |_| {}, + ) + .unwrap(); + assert_view_released(&instance, 3); + }); +} + +#[test] +fn test_buffer_views_release_after_errors() { + Python::attach(|py| { + let instance = Bound::new(py, SelfReferentialBuffer::default()).unwrap(); + + let result = PyBufferView::::with_flags( + &instance, + PyBufferRequest::simple().strides().format(), + |_| panic!("incompatible buffer"), + ); + assert!(result.unwrap_err().is_instance_of::(py)); + assert_view_released(&instance, 1); + + let result = PyUntypedBufferView::with_flags( + &instance, + PyBufferRequest::simple().writable(), + |_| panic!("read-only buffer"), + ); + assert!(result.unwrap_err().is_instance_of::(py)); + assert_view_released(&instance, 1); + }); +} + +#[test] +#[cfg(panic = "unwind")] +fn test_buffer_views_release_on_panic() { + use std::panic::{AssertUnwindSafe, catch_unwind}; + + Python::attach(|py| { + let instance = Bound::new(py, SelfReferentialBuffer::default()).unwrap(); + + let result = catch_unwind(AssertUnwindSafe(|| { + PyUntypedBufferView::with_flags(&instance, PyBufferRequest::full_ro(), |_| { + panic!("buffer callback panicked") + }) + })); + assert!(result.is_err()); + assert_view_released(&instance, 1); + }); +} + /// # Safety /// /// `view` must be a valid pointer to ffi::Py_buffer, or null diff --git a/tests/ui/invalid_buffer_flags.rs b/tests/ui/invalid_buffer_flags.rs new file mode 100644 index 00000000000..3abd27bb0ae --- /dev/null +++ b/tests/ui/invalid_buffer_flags.rs @@ -0,0 +1,24 @@ +use pyo3::buffer::{PyBufferRequest, PyBufferView, PyUntypedBufferView}; +use pyo3::prelude::*; +use pyo3::types::PyBytes; + +fn main() { + let _ = PyBufferRequest::simple().c_contiguous().f_contiguous(); + //~^ ERROR: contiguity has already been constrained for this buffer request + let _ = PyBufferRequest::strided().format().format(); + //~^ ERROR: format information has already been requested for this buffer request + let _ = PyBufferRequest::simple().indirect().indirect(); + //~^ ERROR: suboffsets can only be requested on a direct unconstrained buffer request + + Python::attach(|py| { + let bytes = PyBytes::new(py, &[1, 2, 3]); + PyUntypedBufferView::with_flags(&bytes, PyBufferRequest::strided(), |view| { + view.format(); + //~^ ERROR: format information is not available with the requested buffer flags + }) + .unwrap(); + + PyBufferView::::with_flags(&bytes, PyBufferRequest::strided(), |_| {}); + //~^ ERROR: format information is not available with the requested buffer flags + }); +} diff --git a/tests/ui/invalid_buffer_flags.stderr b/tests/ui/invalid_buffer_flags.stderr new file mode 100644 index 00000000000..4194808d9d3 --- /dev/null +++ b/tests/ui/invalid_buffer_flags.stderr @@ -0,0 +1,87 @@ +error[E0277]: contiguity has already been constrained for this buffer request + --> tests/ui/invalid_buffer_flags.rs:6:54 + | + 6 | let _ = PyBufferRequest::simple().c_contiguous().f_contiguous(); + | ^^^^^^^^^^^^ the trait `buffer::flags::CanRequestContiguity` is not implemented for `buffer::flags::PyBufferFlags` + | + = note: only one of `.c_contiguous()`, `.f_contiguous()`, or `.any_contiguous()` may be used +note: required by a bound in `PyBufferRequest::::f_contiguous` + --> src/buffer.rs + | + | pub const fn f_contiguous(self) -> PyBufferRequest + | ------------ required by a bound in this associated function + | where + | Flag: flags::CanRequestContiguity, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyBufferRequest::::f_contiguous` + +error[E0277]: format information has already been requested for this buffer request + --> tests/ui/invalid_buffer_flags.rs:8:49 + | + 8 | let _ = PyBufferRequest::strided().format().format(); + | ^^^^^^ the trait `buffer::flags::CanRequestFormat` is not implemented for `buffer::flags::PyBufferFlags` + | + = note: remove the extra `.format()` call +note: required by a bound in `PyBufferRequest::::format` + --> src/buffer.rs + | + | pub const fn format(self) -> PyBufferRequest + | ------ required by a bound in this associated function + | where + | Flag: flags::CanRequestFormat, + | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyBufferRequest::::format` + +error[E0277]: suboffsets can only be requested on a direct unconstrained buffer request + --> tests/ui/invalid_buffer_flags.rs:10:50 + | + 10 | let _ = PyBufferRequest::simple().indirect().indirect(); + | ^^^^^^^^ the trait `buffer::flags::CanRequestIndirect` is not implemented for `buffer::flags::PyBufferFlags` + | + = note: call `.indirect()` before any contiguity builder, and only once +note: required by a bound in `PyBufferRequest::::indirect` + --> src/buffer.rs + | + | pub const fn indirect(self) -> PyBufferRequest + | -------- required by a bound in this associated function + | where + | Flag: flags::CanRequestIndirect, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyBufferRequest::::indirect` + +error[E0277]: format information is not available with the requested buffer flags + --> tests/ui/invalid_buffer_flags.rs:16:18 + | + 16 | view.format(); + | ^^^^^^ the trait `buffer::flags::IncludesFormat` is not implemented for `buffer::flags::PyBufferFlags` + | + = note: use `.format()` when building a buffer request to request format information + = note: `PyBufferRequest::simple()` and `PyBufferRequest::simple().writable()` also imply u8 format +note: required by a bound in `PyUntypedBufferView::::format` + --> src/buffer.rs + | + | pub fn format(&self) -> &CStr + | ------ required by a bound in this associated function + | where + | Flag: flags::IncludesFormat, + | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyUntypedBufferView::::format` + +error[E0277]: format information is not available with the requested buffer flags + --> tests/ui/invalid_buffer_flags.rs:21:48 + | + 21 | PyBufferView::::with_flags(&bytes, PyBufferRequest::strided(), |_| {}); + | ------------------------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `buffer::flags::IncludesFormat` is not implemented for `buffer::flags::PyBufferFlags` + | | + | required by a bound introduced by this call + | + = note: use `.format()` when building a buffer request to request format information + = note: `PyBufferRequest::simple()` and `PyBufferRequest::simple().writable()` also imply u8 format +note: required by a bound in `PyBufferView::::with_flags` + --> src/buffer.rs + | + | pub fn with_flags( + | ---------- required by a bound in this associated function +... + | Flag: PyBufferRequestType + flags::IncludesFormat, + | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyBufferView::::with_flags` + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/invalid_buffer_view_slice.rs b/tests/ui/invalid_buffer_view_slice.rs new file mode 100644 index 00000000000..5e25f6dce0f --- /dev/null +++ b/tests/ui/invalid_buffer_view_slice.rs @@ -0,0 +1,36 @@ +use pyo3::buffer::{PyBufferRequest, PyBufferView}; +use pyo3::prelude::*; +use pyo3::types::PyBytes; + +fn main() { + Python::attach(|py| { + let bytes = PyBytes::new(py, &[1, 2, 3]); + PyBufferView::::with(&bytes, |view| { + view.as_contiguous_slice_ptr(); + //~^ ERROR: C contiguity is not guaranteed by the requested buffer flags + view.as_fortran_contiguous_slice_ptr(); + //~^ ERROR: Fortran contiguity is not guaranteed by the requested buffer flags + }) + .unwrap(); + + PyBufferView::::with_flags(&bytes, PyBufferRequest::simple().writable(), |view| { + view.as_contiguous_slice_ptr(); + //~^ ERROR: C contiguity is not guaranteed by the requested buffer flags + view.as_fortran_contiguous_slice_ptr(); + //~^ ERROR: Fortran contiguity is not guaranteed by the requested buffer flags + }) + .unwrap(); + + PyBufferView::::with_flags(&bytes, PyBufferRequest::simple().c_contiguous().format(), |view| { + view.as_fortran_contiguous_slice_ptr(); + //~^ ERROR: Fortran contiguity is not guaranteed by the requested buffer flags + }) + .unwrap(); + + PyBufferView::::with_flags(&bytes, PyBufferRequest::simple().f_contiguous().format(), |view| { + view.as_contiguous_slice_ptr(); + //~^ ERROR: C contiguity is not guaranteed by the requested buffer flags + }) + .unwrap(); + }); +} diff --git a/tests/ui/invalid_buffer_view_slice.stderr b/tests/ui/invalid_buffer_view_slice.stderr new file mode 100644 index 00000000000..ff8b1a15c70 --- /dev/null +++ b/tests/ui/invalid_buffer_view_slice.stderr @@ -0,0 +1,99 @@ +error[E0277]: C contiguity is not guaranteed by the requested buffer flags + --> tests/ui/invalid_buffer_view_slice.rs:9:18 + | + 9 | view.as_contiguous_slice_ptr(); + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `buffer::flags::GuaranteesCContiguous` is not implemented for `buffer::flags::PyBufferFlags` + | + = note: use `.c_contiguous()` when building a buffer request to guarantee C contiguity +note: required by a bound in `PyBufferView::::as_contiguous_slice_ptr` + --> src/buffer.rs + | + | pub fn as_contiguous_slice_ptr(&self) -> NonNull<[T]> + | ----------------------- required by a bound in this associated function + | where + | Flag: flags::GuaranteesCContiguous, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyBufferView::::as_contiguous_slice_ptr` + +error[E0277]: Fortran contiguity is not guaranteed by the requested buffer flags + --> tests/ui/invalid_buffer_view_slice.rs:11:18 + | + 11 | view.as_fortran_contiguous_slice_ptr(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `buffer::flags::GuaranteesFContiguous` is not implemented for `buffer::flags::PyBufferFlags` + | + = note: use `.f_contiguous()` when building a buffer request to guarantee Fortran contiguity +note: required by a bound in `PyBufferView::::as_fortran_contiguous_slice_ptr` + --> src/buffer.rs + | + | pub fn as_fortran_contiguous_slice_ptr(&self) -> NonNull<[T]> + | ------------------------------- required by a bound in this associated function + | where + | Flag: flags::GuaranteesFContiguous, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyBufferView::::as_fortran_contiguous_slice_ptr` + +error[E0277]: C contiguity is not guaranteed by the requested buffer flags + --> tests/ui/invalid_buffer_view_slice.rs:17:18 + | + 17 | view.as_contiguous_slice_ptr(); + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `buffer::flags::GuaranteesCContiguous` is not implemented for `buffer::flags::PyBufferFlags` + | + = note: use `.c_contiguous()` when building a buffer request to guarantee C contiguity +note: required by a bound in `PyBufferView::::as_contiguous_slice_ptr` + --> src/buffer.rs + | + | pub fn as_contiguous_slice_ptr(&self) -> NonNull<[T]> + | ----------------------- required by a bound in this associated function + | where + | Flag: flags::GuaranteesCContiguous, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyBufferView::::as_contiguous_slice_ptr` + +error[E0277]: Fortran contiguity is not guaranteed by the requested buffer flags + --> tests/ui/invalid_buffer_view_slice.rs:19:18 + | + 19 | view.as_fortran_contiguous_slice_ptr(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `buffer::flags::GuaranteesFContiguous` is not implemented for `buffer::flags::PyBufferFlags` + | + = note: use `.f_contiguous()` when building a buffer request to guarantee Fortran contiguity +note: required by a bound in `PyBufferView::::as_fortran_contiguous_slice_ptr` + --> src/buffer.rs + | + | pub fn as_fortran_contiguous_slice_ptr(&self) -> NonNull<[T]> + | ------------------------------- required by a bound in this associated function + | where + | Flag: flags::GuaranteesFContiguous, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyBufferView::::as_fortran_contiguous_slice_ptr` + +error[E0277]: Fortran contiguity is not guaranteed by the requested buffer flags + --> tests/ui/invalid_buffer_view_slice.rs:25:18 + | + 25 | view.as_fortran_contiguous_slice_ptr(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `buffer::flags::GuaranteesFContiguous` is not implemented for `buffer::flags::PyBufferFlags` + | + = note: use `.f_contiguous()` when building a buffer request to guarantee Fortran contiguity +note: required by a bound in `PyBufferView::::as_fortran_contiguous_slice_ptr` + --> src/buffer.rs + | + | pub fn as_fortran_contiguous_slice_ptr(&self) -> NonNull<[T]> + | ------------------------------- required by a bound in this associated function + | where + | Flag: flags::GuaranteesFContiguous, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyBufferView::::as_fortran_contiguous_slice_ptr` + +error[E0277]: C contiguity is not guaranteed by the requested buffer flags + --> tests/ui/invalid_buffer_view_slice.rs:31:18 + | + 31 | view.as_contiguous_slice_ptr(); + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `buffer::flags::GuaranteesCContiguous` is not implemented for `buffer::flags::PyBufferFlags` + | + = note: use `.c_contiguous()` when building a buffer request to guarantee C contiguity +note: required by a bound in `PyBufferView::::as_contiguous_slice_ptr` + --> src/buffer.rs + | + | pub fn as_contiguous_slice_ptr(&self) -> NonNull<[T]> + | ----------------------- required by a bound in this associated function + | where + | Flag: flags::GuaranteesCContiguous, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PyBufferView::::as_contiguous_slice_ptr` + +error: aborting due to 6 previous errors + +For more information about this error, try `rustc --explain E0277`.