use crate::err::{PyErr, PyResult};
use crate::ffi;
use crate::ffi_ptr_ext::FfiPtrExt;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
#[cfg(feature = "experimental-inspect")]
use crate::type_object::PyTypeInfo;
use crate::types::{PyRange, PyRangeMethods};
#[cfg(RustPython)]
use crate::{
sync::PyOnceLock,
types::{PyType, PyTypeMethods},
Py,
};
use crate::{Bound, IntoPyObject, PyAny, Python};
use core::convert::Infallible;
#[repr(transparent)]
pub struct PySlice(PyAny);
#[cfg(not(RustPython))]
pyobject_native_type!(
PySlice,
ffi::PySliceObject,
pyobject_native_static_type_object!(ffi::PySlice_Type),
"builtins",
"slice",
#checkfunction=ffi::PySlice_Check
);
#[cfg(RustPython)]
pyobject_native_type!(
PySlice,
ffi::PySliceObject,
|py| {
static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
TYPE.import(py, "builtins", "slice").unwrap().as_type_ptr()
},
"builtins",
"slice",
#checkfunction=ffi::PySlice_Check
);
#[derive(Debug, Eq, PartialEq)]
pub struct PySliceIndices {
pub start: isize,
pub stop: isize,
pub step: isize,
pub slicelength: usize,
}
impl PySliceIndices {
pub fn new(start: isize, stop: isize, step: isize) -> PySliceIndices {
PySliceIndices {
start,
stop,
step,
slicelength: 0,
}
}
}
impl PySlice {
pub fn new(py: Python<'_>, start: isize, stop: isize, step: isize) -> Bound<'_, PySlice> {
unsafe {
ffi::PySlice_New(
ffi::PyLong_FromSsize_t(start),
ffi::PyLong_FromSsize_t(stop),
ffi::PyLong_FromSsize_t(step),
)
.assume_owned(py)
.cast_into_unchecked()
}
}
pub fn full(py: Python<'_>) -> Bound<'_, PySlice> {
unsafe {
ffi::PySlice_New(ffi::Py_None(), ffi::Py_None(), ffi::Py_None())
.assume_owned(py)
.cast_into_unchecked()
}
}
}
#[doc(alias = "PySlice")]
pub trait PySliceMethods<'py>: crate::sealed::Sealed {
fn indices(&self, length: isize) -> PyResult<PySliceIndices>;
}
impl<'py> PySliceMethods<'py> for Bound<'py, PySlice> {
fn indices(&self, length: isize) -> PyResult<PySliceIndices> {
unsafe {
let mut slicelength: isize = 0;
let mut start: isize = 0;
let mut stop: isize = 0;
let mut step: isize = 0;
let r = ffi::PySlice_GetIndicesEx(
self.as_ptr(),
length,
&mut start,
&mut stop,
&mut step,
&mut slicelength,
);
if r == 0 {
Ok(PySliceIndices {
start,
stop,
step,
slicelength: slicelength as _,
})
} else {
Err(PyErr::fetch(self.py()))
}
}
}
}
impl<'py> IntoPyObject<'py> for PySliceIndices {
type Target = PySlice;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PySlice::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PySlice::new(py, self.start, self.stop, self.step))
}
}
impl<'py> IntoPyObject<'py> for &PySliceIndices {
type Target = PySlice;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PySlice::TYPE_HINT;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PySlice::new(py, self.start, self.stop, self.step))
}
}
impl<'py> TryFrom<Bound<'py, PyRange>> for Bound<'py, PySlice> {
type Error = PyErr;
fn try_from(range: Bound<'py, PyRange>) -> Result<Self, Self::Error> {
Ok(PySlice::new(
range.py(),
range.start()?,
range.stop()?,
range.step()?,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::PyAnyMethods as _;
#[test]
fn test_py_slice_new() {
Python::attach(|py| {
let slice = PySlice::new(py, isize::MIN, isize::MAX, 1);
assert_eq!(
slice.getattr("start").unwrap().extract::<isize>().unwrap(),
isize::MIN
);
assert_eq!(
slice.getattr("stop").unwrap().extract::<isize>().unwrap(),
isize::MAX
);
assert_eq!(
slice.getattr("step").unwrap().extract::<isize>().unwrap(),
1
);
});
}
#[test]
fn test_py_slice_full() {
Python::attach(|py| {
let slice = PySlice::full(py);
assert!(slice.getattr("start").unwrap().is_none(),);
assert!(slice.getattr("stop").unwrap().is_none(),);
assert!(slice.getattr("step").unwrap().is_none(),);
assert_eq!(
slice.indices(0).unwrap(),
PySliceIndices {
start: 0,
stop: 0,
step: 1,
slicelength: 0,
},
);
assert_eq!(
slice.indices(42).unwrap(),
PySliceIndices {
start: 0,
stop: 42,
step: 1,
slicelength: 42,
},
);
});
}
#[test]
fn test_py_slice_indices_new() {
let start = 0;
let stop = 0;
let step = 0;
assert_eq!(
PySliceIndices::new(start, stop, step),
PySliceIndices {
start,
stop,
step,
slicelength: 0
}
);
let start = 0;
let stop = 100;
let step = 10;
assert_eq!(
PySliceIndices::new(start, stop, step),
PySliceIndices {
start,
stop,
step,
slicelength: 0
}
);
let start = 0;
let stop = -10;
let step = -1;
assert_eq!(
PySliceIndices::new(start, stop, step),
PySliceIndices {
start,
stop,
step,
slicelength: 0
}
);
let start = 0;
let stop = -10;
let step = 20;
assert_eq!(
PySliceIndices::new(start, stop, step),
PySliceIndices {
start,
stop,
step,
slicelength: 0
}
);
}
}