Skip to content

Commit 3a39844

Browse files
Add PyRange wrapper (#5117)
* Add `PyRange` wrapper with conversions to/from `std::ops::Range` * `IntoPyObject` for `RangeFull` `RangeFrom` `RangeTo` `RangeToInclusive` * Remove conversions & address comments
1 parent e613a79 commit 3a39844

File tree

5 files changed

+112
-2
lines changed

5 files changed

+112
-2
lines changed

newsfragments/5117.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `PyRange` wrapper.

src/sealed.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::types::{
22
PyBool, PyByteArray, PyBytes, PyCapsule, PyComplex, PyDict, PyFloat, PyFrozenSet, PyList,
3-
PyMapping, PyMappingProxy, PyModule, PySequence, PySet, PySlice, PyString, PyTraceback,
4-
PyTuple, PyType, PyWeakref, PyWeakrefProxy, PyWeakrefReference,
3+
PyMapping, PyMappingProxy, PyModule, PyRange, PySequence, PySet, PySlice, PyString,
4+
PyTraceback, PyTuple, PyType, PyWeakref, PyWeakrefProxy, PyWeakrefReference,
55
};
66
use crate::{ffi, Bound, PyAny, PyResult};
77

@@ -35,6 +35,7 @@ impl Sealed for Bound<'_, PyList> {}
3535
impl Sealed for Bound<'_, PyMapping> {}
3636
impl Sealed for Bound<'_, PyMappingProxy> {}
3737
impl Sealed for Bound<'_, PyModule> {}
38+
impl Sealed for Bound<'_, PyRange> {}
3839
impl Sealed for Bound<'_, PySequence> {}
3940
impl Sealed for Bound<'_, PySet> {}
4041
impl Sealed for Bound<'_, PySlice> {}

src/types/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub use self::notimplemented::PyNotImplemented;
3838
pub use self::num::PyInt;
3939
#[cfg(not(any(PyPy, GraalPy)))]
4040
pub use self::pysuper::PySuper;
41+
pub use self::range::{PyRange, PyRangeMethods};
4142
pub use self::sequence::{PySequence, PySequenceMethods};
4243
pub use self::set::{PySet, PySetMethods};
4344
pub use self::slice::{PySlice, PySliceIndices, PySliceMethods};
@@ -246,6 +247,7 @@ mod notimplemented;
246247
mod num;
247248
#[cfg(not(any(PyPy, GraalPy)))]
248249
mod pysuper;
250+
pub(crate) mod range;
249251
pub(crate) mod sequence;
250252
pub(crate) mod set;
251253
pub(crate) mod slice;

src/types/range.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use crate::sealed::Sealed;
2+
use crate::types::PyAnyMethods;
3+
use crate::{ffi, Bound, PyAny, PyResult, PyTypeInfo, Python};
4+
5+
/// Represents a Python `range`.
6+
///
7+
/// Values of this type are accessed via PyO3's smart pointers, e.g. as
8+
/// [`Py<PyRange>`][crate::Py] or [`Bound<'py, PyRange>`][Bound].
9+
///
10+
/// For APIs available on `range` objects, see the [`PyRangeMethods`] trait which is implemented for
11+
/// [`Bound<'py, PyRange>`][Bound].
12+
#[repr(transparent)]
13+
pub struct PyRange(PyAny);
14+
15+
pyobject_native_type_core!(PyRange, pyobject_native_static_type_object!(ffi::PyRange_Type), #checkfunction=ffi::PyRange_Check);
16+
17+
impl<'py> PyRange {
18+
/// Creates a new Python `range` object with a default step of 1.
19+
pub fn new(py: Python<'py>, start: isize, stop: isize) -> PyResult<Bound<'py, Self>> {
20+
Self::new_with_step(py, start, stop, 1)
21+
}
22+
23+
/// Creates a new Python `range` object with a specified step.
24+
pub fn new_with_step(
25+
py: Python<'py>,
26+
start: isize,
27+
stop: isize,
28+
step: isize,
29+
) -> PyResult<Bound<'py, Self>> {
30+
unsafe {
31+
Ok(Self::type_object(py)
32+
.call1((start, stop, step))?
33+
.downcast_into_unchecked())
34+
}
35+
}
36+
}
37+
38+
/// Implementation of functionality for [`PyRange`].
39+
///
40+
/// These methods are defined for the `Bound<'py, PyRange>` smart pointer, so to use method call
41+
/// syntax these methods are separated into a trait, because stable Rust does not yet support
42+
/// `arbitrary_self_types`.
43+
#[doc(alias = "PyRange")]
44+
pub trait PyRangeMethods<'py>: Sealed {
45+
/// Returns the start of the range.
46+
fn start(&self) -> PyResult<isize>;
47+
48+
/// Returns the exclusive end of the range.
49+
fn stop(&self) -> PyResult<isize>;
50+
51+
/// Returns the step of the range.
52+
fn step(&self) -> PyResult<isize>;
53+
}
54+
55+
impl<'py> PyRangeMethods<'py> for Bound<'py, PyRange> {
56+
fn start(&self) -> PyResult<isize> {
57+
self.getattr(intern!(self.py(), "start"))?.extract()
58+
}
59+
60+
fn stop(&self) -> PyResult<isize> {
61+
self.getattr(intern!(self.py(), "stop"))?.extract()
62+
}
63+
64+
fn step(&self) -> PyResult<isize> {
65+
self.getattr(intern!(self.py(), "step"))?.extract()
66+
}
67+
}
68+
69+
#[cfg(test)]
70+
mod tests {
71+
use super::*;
72+
73+
#[test]
74+
fn test_py_range_new() {
75+
Python::with_gil(|py| {
76+
let range = PyRange::new(py, isize::MIN, isize::MAX).unwrap();
77+
assert_eq!(range.start().unwrap(), isize::MIN);
78+
assert_eq!(range.stop().unwrap(), isize::MAX);
79+
assert_eq!(range.step().unwrap(), 1);
80+
});
81+
}
82+
83+
#[test]
84+
fn test_py_range_new_with_step() {
85+
Python::with_gil(|py| {
86+
let range = PyRange::new_with_step(py, 1, 10, 2).unwrap();
87+
assert_eq!(range.start().unwrap(), 1);
88+
assert_eq!(range.stop().unwrap(), 10);
89+
assert_eq!(range.step().unwrap(), 2);
90+
});
91+
}
92+
}

src/types/slice.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::err::{PyErr, PyResult};
22
use crate::ffi;
33
use crate::ffi_ptr_ext::FfiPtrExt;
44
use crate::types::any::PyAnyMethods;
5+
use crate::types::{PyRange, PyRangeMethods};
56
use crate::{Bound, IntoPyObject, PyAny, Python};
67
use std::convert::Infallible;
78

@@ -140,6 +141,19 @@ impl<'py> IntoPyObject<'py> for &PySliceIndices {
140141
}
141142
}
142143

144+
impl<'py> TryFrom<Bound<'py, PyRange>> for Bound<'py, PySlice> {
145+
type Error = PyErr;
146+
147+
fn try_from(range: Bound<'py, PyRange>) -> Result<Self, Self::Error> {
148+
Ok(PySlice::new(
149+
range.py(),
150+
range.start()?,
151+
range.stop()?,
152+
range.step()?,
153+
))
154+
}
155+
}
156+
143157
#[cfg(test)]
144158
mod tests {
145159
use super::*;

0 commit comments

Comments
 (0)