Skip to main content

pyo3/types/
slice.rs

1use crate::err::{PyErr, PyResult};
2use crate::ffi;
3use crate::ffi_ptr_ext::FfiPtrExt;
4#[cfg(feature = "experimental-inspect")]
5use crate::inspect::PyStaticExpr;
6#[cfg(feature = "experimental-inspect")]
7use crate::type_object::PyTypeInfo;
8use crate::types::{PyRange, PyRangeMethods};
9#[cfg(RustPython)]
10use crate::{
11    sync::PyOnceLock,
12    types::{PyType, PyTypeMethods},
13    Py,
14};
15use crate::{Bound, IntoPyObject, PyAny, Python};
16use core::convert::Infallible;
17
18/// Represents a Python `slice`.
19///
20/// Values of this type are accessed via PyO3's smart pointers, e.g. as
21/// [`Py<PySlice>`][crate::Py] or [`Bound<'py, PySlice>`][Bound].
22///
23/// For APIs available on `slice` objects, see the [`PySliceMethods`] trait which is implemented for
24/// [`Bound<'py, PySlice>`][Bound].
25///
26/// Only `isize` indices supported at the moment by the `PySlice` object.
27#[repr(transparent)]
28pub struct PySlice(PyAny);
29
30#[cfg(not(RustPython))]
31pyobject_native_type!(
32    PySlice,
33    ffi::PySliceObject,
34    pyobject_native_static_type_object!(ffi::PySlice_Type),
35    "builtins",
36    "slice",
37    #checkfunction=ffi::PySlice_Check
38);
39
40#[cfg(RustPython)]
41pyobject_native_type!(
42    PySlice,
43    ffi::PySliceObject,
44    |py| {
45        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
46        TYPE.import(py, "builtins", "slice").unwrap().as_type_ptr()
47    },
48    "builtins",
49    "slice",
50    #checkfunction=ffi::PySlice_Check
51);
52
53/// Return value from [`PySliceMethods::indices`].
54#[derive(Debug, Eq, PartialEq)]
55pub struct PySliceIndices {
56    /// Start of the slice
57    ///
58    /// It can be -1 when the step is negative, otherwise it's non-negative.
59    pub start: isize,
60    /// End of the slice
61    ///
62    /// It can be -1 when the step is negative, otherwise it's non-negative.
63    pub stop: isize,
64    /// Increment to use when iterating the slice from `start` to `stop`.
65    pub step: isize,
66    /// The length of the slice calculated from the original input sequence.
67    pub slicelength: usize,
68}
69
70impl PySliceIndices {
71    /// Creates a new `PySliceIndices`.
72    pub fn new(start: isize, stop: isize, step: isize) -> PySliceIndices {
73        PySliceIndices {
74            start,
75            stop,
76            step,
77            slicelength: 0,
78        }
79    }
80}
81
82impl PySlice {
83    /// Constructs a new slice with the given elements.
84    pub fn new(py: Python<'_>, start: isize, stop: isize, step: isize) -> Bound<'_, PySlice> {
85        unsafe {
86            ffi::PySlice_New(
87                ffi::PyLong_FromSsize_t(start),
88                ffi::PyLong_FromSsize_t(stop),
89                ffi::PyLong_FromSsize_t(step),
90            )
91            .assume_owned(py)
92            .cast_into_unchecked()
93        }
94    }
95
96    /// Constructs a new full slice that is equivalent to `::`.
97    pub fn full(py: Python<'_>) -> Bound<'_, PySlice> {
98        unsafe {
99            ffi::PySlice_New(ffi::Py_None(), ffi::Py_None(), ffi::Py_None())
100                .assume_owned(py)
101                .cast_into_unchecked()
102        }
103    }
104}
105
106/// Implementation of functionality for [`PySlice`].
107///
108/// These methods are defined for the `Bound<'py, PyTuple>` smart pointer, so to use method call
109/// syntax these methods are separated into a trait, because stable Rust does not yet support
110/// `arbitrary_self_types`.
111#[doc(alias = "PySlice")]
112pub trait PySliceMethods<'py>: crate::sealed::Sealed {
113    /// Retrieves the start, stop, and step indices from the slice object,
114    /// assuming a sequence of length `length`, and stores the length of the
115    /// slice in its `slicelength` member.
116    fn indices(&self, length: isize) -> PyResult<PySliceIndices>;
117}
118
119impl<'py> PySliceMethods<'py> for Bound<'py, PySlice> {
120    fn indices(&self, length: isize) -> PyResult<PySliceIndices> {
121        unsafe {
122            let mut slicelength: isize = 0;
123            let mut start: isize = 0;
124            let mut stop: isize = 0;
125            let mut step: isize = 0;
126            let r = ffi::PySlice_GetIndicesEx(
127                self.as_ptr(),
128                length,
129                &mut start,
130                &mut stop,
131                &mut step,
132                &mut slicelength,
133            );
134            if r == 0 {
135                Ok(PySliceIndices {
136                    start,
137                    stop,
138                    step,
139                    // non-negative isize should always fit into usize
140                    slicelength: slicelength as _,
141                })
142            } else {
143                Err(PyErr::fetch(self.py()))
144            }
145        }
146    }
147}
148
149impl<'py> IntoPyObject<'py> for PySliceIndices {
150    type Target = PySlice;
151    type Output = Bound<'py, Self::Target>;
152    type Error = Infallible;
153
154    #[cfg(feature = "experimental-inspect")]
155    const OUTPUT_TYPE: PyStaticExpr = PySlice::TYPE_HINT;
156
157    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
158        Ok(PySlice::new(py, self.start, self.stop, self.step))
159    }
160}
161
162impl<'py> IntoPyObject<'py> for &PySliceIndices {
163    type Target = PySlice;
164    type Output = Bound<'py, Self::Target>;
165    type Error = Infallible;
166
167    #[cfg(feature = "experimental-inspect")]
168    const OUTPUT_TYPE: PyStaticExpr = PySlice::TYPE_HINT;
169
170    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
171        Ok(PySlice::new(py, self.start, self.stop, self.step))
172    }
173}
174
175impl<'py> TryFrom<Bound<'py, PyRange>> for Bound<'py, PySlice> {
176    type Error = PyErr;
177
178    fn try_from(range: Bound<'py, PyRange>) -> Result<Self, Self::Error> {
179        Ok(PySlice::new(
180            range.py(),
181            range.start()?,
182            range.stop()?,
183            range.step()?,
184        ))
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::types::PyAnyMethods as _;
192
193    #[test]
194    fn test_py_slice_new() {
195        Python::attach(|py| {
196            let slice = PySlice::new(py, isize::MIN, isize::MAX, 1);
197            assert_eq!(
198                slice.getattr("start").unwrap().extract::<isize>().unwrap(),
199                isize::MIN
200            );
201            assert_eq!(
202                slice.getattr("stop").unwrap().extract::<isize>().unwrap(),
203                isize::MAX
204            );
205            assert_eq!(
206                slice.getattr("step").unwrap().extract::<isize>().unwrap(),
207                1
208            );
209        });
210    }
211
212    #[test]
213    fn test_py_slice_full() {
214        Python::attach(|py| {
215            let slice = PySlice::full(py);
216            assert!(slice.getattr("start").unwrap().is_none(),);
217            assert!(slice.getattr("stop").unwrap().is_none(),);
218            assert!(slice.getattr("step").unwrap().is_none(),);
219            assert_eq!(
220                slice.indices(0).unwrap(),
221                PySliceIndices {
222                    start: 0,
223                    stop: 0,
224                    step: 1,
225                    slicelength: 0,
226                },
227            );
228            assert_eq!(
229                slice.indices(42).unwrap(),
230                PySliceIndices {
231                    start: 0,
232                    stop: 42,
233                    step: 1,
234                    slicelength: 42,
235                },
236            );
237        });
238    }
239
240    #[test]
241    fn test_py_slice_indices_new() {
242        let start = 0;
243        let stop = 0;
244        let step = 0;
245        assert_eq!(
246            PySliceIndices::new(start, stop, step),
247            PySliceIndices {
248                start,
249                stop,
250                step,
251                slicelength: 0
252            }
253        );
254
255        let start = 0;
256        let stop = 100;
257        let step = 10;
258        assert_eq!(
259            PySliceIndices::new(start, stop, step),
260            PySliceIndices {
261                start,
262                stop,
263                step,
264                slicelength: 0
265            }
266        );
267
268        let start = 0;
269        let stop = -10;
270        let step = -1;
271        assert_eq!(
272            PySliceIndices::new(start, stop, step),
273            PySliceIndices {
274                start,
275                stop,
276                step,
277                slicelength: 0
278            }
279        );
280
281        let start = 0;
282        let stop = -10;
283        let step = 20;
284        assert_eq!(
285            PySliceIndices::new(start, stop, step),
286            PySliceIndices {
287                start,
288                stop,
289                step,
290                slicelength: 0
291            }
292        );
293    }
294}