Skip to content

Commit cd83a91

Browse files
committed
list,tuple,sequence: add slice indexing
1 parent 2df0cb6 commit cd83a91

File tree

6 files changed

+337
-38
lines changed

6 files changed

+337
-38
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1212

1313
- Add `PyList::get_item_unchecked()` and `PyTuple::get_item_unchecked()` to get items without bounds checks. [#1733](https://github.com/PyO3/pyo3/pull/1733)
1414
- Add `PyAny::py()` as a convenience for `PyNativeType::py()`. [#1751](https://github.com/PyO3/pyo3/pull/1751)
15-
- Add implementation of `std::ops::Index` for `PyList`, `PyTuple` and `PySequence`. [#1825](https://github.com/PyO3/pyo3/pull/1825)
15+
- Add implementation of `std::ops::Index<usize>` for `PyList`, `PyTuple` and `PySequence`. [#1825](https://github.com/PyO3/pyo3/pull/1825)
16+
- Add range indexing implementations of `std::ops::Index` for `PyList`, `PyTuple` and `PySequence`. [#1829](https://github.com/PyO3/pyo3/pull/1829)
1617

1718
### Changed
1819

build.rs

+5
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ fn configure_pyo3() -> Result<()> {
137137

138138
let rustc_minor_version = rustc_minor_version().unwrap_or(0);
139139

140+
// Enable use of #[track_caller] on Rust 1.46 and greater
141+
if rustc_minor_version >= 46 {
142+
println!("cargo:rustc-cfg=track_caller");
143+
}
144+
140145
// Enable use of const generics on Rust 1.51 and greater
141146
if rustc_minor_version >= 51 {
142147
println!("cargo:rustc-cfg=min_const_generics");

src/internal_tricks.rs

+143
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,146 @@ pub(crate) fn extract_cstr_or_leak_cstring(
5858
pub(crate) fn get_ssize_index(index: usize) -> Py_ssize_t {
5959
index.min(PY_SSIZE_T_MAX as usize) as Py_ssize_t
6060
}
61+
62+
/// Implementations used for slice indexing PySequence, PyTuple, and PyList
63+
macro_rules! index_impls {
64+
(
65+
$ty:ty,
66+
$output:ty,
67+
$ty_name:literal,
68+
$len:expr,
69+
$get_slice:expr,
70+
) => {
71+
impl Index<usize> for $ty {
72+
// Always PyAny output (even if the slice operation returns something else)
73+
type Output = PyAny;
74+
75+
#[cfg_attr(track_caller, track_caller)]
76+
fn index(&self, index: usize) -> &Self::Output {
77+
self.get_item(index).unwrap_or_else(|_| {
78+
crate::internal_tricks::index_len_fail(index, $ty_name, $len(self));
79+
unreachable!()
80+
})
81+
}
82+
}
83+
84+
impl Index<Range<usize>> for $ty {
85+
type Output = $output;
86+
87+
#[cfg_attr(track_caller, track_caller)]
88+
fn index(&self, Range { start, end }: Range<usize>) -> &Self::Output {
89+
let len = $len(self);
90+
if start > len {
91+
crate::internal_tricks::slice_start_index_len_fail(start, $ty_name, len);
92+
unreachable!()
93+
} else if end > len {
94+
crate::internal_tricks::slice_end_index_len_fail(end, $ty_name, len);
95+
unreachable!()
96+
} else if start > end {
97+
crate::internal_tricks::slice_index_order_fail(start, end);
98+
unreachable!()
99+
} else {
100+
$get_slice(self, start, end)
101+
}
102+
}
103+
}
104+
105+
impl Index<RangeFrom<usize>> for $ty {
106+
type Output = $output;
107+
108+
#[cfg_attr(track_caller, track_caller)]
109+
fn index(&self, RangeFrom { start }: RangeFrom<usize>) -> &Self::Output {
110+
let len = $len(self);
111+
if start > len {
112+
crate::internal_tricks::slice_start_index_len_fail(start, $ty_name, len);
113+
unreachable!()
114+
} else {
115+
$get_slice(self, start, len)
116+
}
117+
}
118+
}
119+
120+
impl Index<RangeFull> for $ty {
121+
type Output = $output;
122+
123+
#[cfg_attr(track_caller, track_caller)]
124+
fn index(&self, _: RangeFull) -> &Self::Output {
125+
let len = $len(self);
126+
$get_slice(self, 0, len)
127+
}
128+
}
129+
130+
impl Index<RangeInclusive<usize>> for $ty {
131+
type Output = $output;
132+
133+
#[cfg_attr(track_caller, track_caller)]
134+
fn index(&self, range: RangeInclusive<usize>) -> &Self::Output {
135+
let exclusive_end = range
136+
.end()
137+
.checked_add(1)
138+
.expect("range end exceeds Python limit");
139+
&self[*range.start()..exclusive_end]
140+
}
141+
}
142+
143+
impl Index<RangeTo<usize>> for $ty {
144+
type Output = $output;
145+
146+
#[cfg_attr(track_caller, track_caller)]
147+
fn index(&self, RangeTo { end }: RangeTo<usize>) -> &Self::Output {
148+
&self[0..end]
149+
}
150+
}
151+
152+
impl Index<RangeToInclusive<usize>> for $ty {
153+
type Output = $output;
154+
155+
#[cfg_attr(track_caller, track_caller)]
156+
fn index(&self, RangeToInclusive { end }: RangeToInclusive<usize>) -> &Self::Output {
157+
&self[0..=end]
158+
}
159+
}
160+
};
161+
}
162+
163+
// nb these would all return ! (the never type) if it was stable, which would remove need for the
164+
// unreachable!() uses above
165+
166+
// these error messages are shamelessly "borrowed" from std.
167+
168+
#[inline(never)]
169+
#[cold]
170+
#[cfg_attr(track_caller, track_caller)]
171+
pub(crate) fn index_len_fail(index: usize, ty_name: &str, len: usize) {
172+
panic!(
173+
"index {} out of range for {} of length {}",
174+
index, ty_name, len
175+
);
176+
}
177+
178+
#[inline(never)]
179+
#[cold]
180+
#[cfg_attr(track_caller, track_caller)]
181+
pub(crate) fn slice_start_index_len_fail(index: usize, ty_name: &str, len: usize) {
182+
panic!(
183+
"range start index {} out of range for {} of length {}",
184+
index, ty_name, len
185+
);
186+
}
187+
188+
#[inline(never)]
189+
#[cold]
190+
#[cfg_attr(track_caller, track_caller)]
191+
pub(crate) fn slice_end_index_len_fail(index: usize, ty_name: &str, len: usize) {
192+
panic!(
193+
"range end index {} out of range for {} of length {}",
194+
index, ty_name, len
195+
);
196+
}
197+
198+
#[inline(never)]
199+
#[cold]
200+
#[cfg_attr(track_caller, track_caller)]
201+
pub(crate) fn slice_index_order_fail(index: usize, end: usize) {
202+
panic!("slice index starts at {} but ends at {}", index, end);
203+
}

src/types/list.rs

+53-14
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::internal_tricks::get_ssize_index;
88
use crate::{
99
AsPyPointer, IntoPy, IntoPyPointer, PyAny, PyObject, Python, ToBorrowedObject, ToPyObject,
1010
};
11-
use std::ops::Index;
11+
use std::ops::{Index, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};
1212

1313
/// Represents a Python `list`.
1414
#[repr(transparent)]
@@ -177,19 +177,7 @@ impl PyList {
177177
}
178178
}
179179

180-
impl Index<usize> for PyList {
181-
type Output = PyAny;
182-
183-
fn index(&self, index: usize) -> &Self::Output {
184-
self.get_item(index).unwrap_or_else(|_| {
185-
panic!(
186-
"index {} out of range for list of length {}",
187-
index,
188-
self.len()
189-
);
190-
})
191-
}
192-
}
180+
index_impls!(PyList, PyList, "list", PyList::len, PyList::get_slice,);
193181

194182
/// Used by `PyList::iter()`.
195183
pub struct PyListIterator<'a> {
@@ -544,4 +532,55 @@ mod tests {
544532
let _ = &list[7];
545533
});
546534
}
535+
536+
#[test]
537+
fn test_list_index_trait_ranges() {
538+
Python::with_gil(|py| {
539+
let list = PyList::new(py, &[2, 3, 5]);
540+
assert_eq!(vec![3, 5], list[1..3].extract::<Vec<i32>>().unwrap());
541+
assert_eq!(Vec::<i32>::new(), list[3..3].extract::<Vec<i32>>().unwrap());
542+
assert_eq!(vec![3, 5], list[1..].extract::<Vec<i32>>().unwrap());
543+
assert_eq!(Vec::<i32>::new(), list[3..].extract::<Vec<i32>>().unwrap());
544+
assert_eq!(vec![2, 3, 5], list[..].extract::<Vec<i32>>().unwrap());
545+
assert_eq!(vec![3, 5], list[1..=2].extract::<Vec<i32>>().unwrap());
546+
assert_eq!(vec![2, 3], list[..2].extract::<Vec<i32>>().unwrap());
547+
assert_eq!(vec![2, 3], list[..=1].extract::<Vec<i32>>().unwrap());
548+
})
549+
}
550+
551+
#[test]
552+
#[should_panic = "range start index 5 out of range for list of length 3"]
553+
fn test_list_index_trait_range_panic_lower() {
554+
Python::with_gil(|py| {
555+
let list = PyList::new(py, &[2, 3, 5]);
556+
list[5..3].extract::<Vec<i32>>().unwrap();
557+
})
558+
}
559+
560+
#[test]
561+
#[should_panic = "range end index 10 out of range for list of length 3"]
562+
fn test_list_index_trait_range_panic_upper() {
563+
Python::with_gil(|py| {
564+
let list = PyList::new(py, &[2, 3, 5]);
565+
list[1..10].extract::<Vec<i32>>().unwrap();
566+
})
567+
}
568+
569+
#[test]
570+
#[should_panic = "slice index starts at 2 but ends at 1"]
571+
fn test_list_index_trait_range_panic_wrong_order() {
572+
Python::with_gil(|py| {
573+
let list = PyList::new(py, &[2, 3, 5]);
574+
list[2..1].extract::<Vec<i32>>().unwrap();
575+
})
576+
}
577+
578+
#[test]
579+
#[should_panic = "range start index 8 out of range for list of length 3"]
580+
fn test_list_index_trait_range_from_panic() {
581+
Python::with_gil(|py| {
582+
let list = PyList::new(py, &[2, 3, 5]);
583+
list[8..].extract::<Vec<i32>>().unwrap();
584+
})
585+
}
547586
}

src/types/sequence.rs

+73-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::internal_tricks::get_ssize_index;
66
use crate::types::{PyAny, PyList, PyTuple};
77
use crate::AsPyPointer;
88
use crate::{FromPyObject, PyTryFrom, ToBorrowedObject};
9-
use std::ops::Index;
9+
use std::ops::{Index, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};
1010

1111
/// Represents a reference to a Python object supporting the sequence protocol.
1212
#[repr(transparent)]
@@ -254,16 +254,19 @@ impl PySequence {
254254
}
255255
}
256256

257-
impl Index<usize> for PySequence {
258-
type Output = PyAny;
257+
#[inline]
258+
fn sequence_len(seq: &PySequence) -> usize {
259+
seq.len().expect("failed to get sequence length")
260+
}
259261

260-
fn index(&self, index: usize) -> &Self::Output {
261-
self.get_item(index).unwrap_or_else(|_| {
262-
panic!("index {} out of range for sequence", index);
263-
})
264-
}
262+
#[inline]
263+
fn sequence_slice(seq: &PySequence, start: usize, end: usize) -> &PyAny {
264+
seq.get_slice(start, end)
265+
.expect("sequence slice operation failed")
265266
}
266267

268+
index_impls!(PySequence, PyAny, "sequence", sequence_len, sequence_slice,);
269+
267270
impl<'a, T> FromPyObject<'a> for Vec<T>
268271
where
269272
T: FromPyObject<'a>,
@@ -439,7 +442,7 @@ mod tests {
439442
}
440443

441444
#[test]
442-
#[should_panic]
445+
#[should_panic = "index 7 out of range for sequence"]
443446
fn test_seq_index_trait_panic() {
444447
Python::with_gil(|py| {
445448
let v: Vec<i32> = vec![1, 1, 2];
@@ -449,6 +452,67 @@ mod tests {
449452
});
450453
}
451454

455+
#[test]
456+
fn test_seq_index_trait_ranges() {
457+
Python::with_gil(|py| {
458+
let v: Vec<i32> = vec![1, 1, 2];
459+
let ob = v.to_object(py);
460+
let seq = ob.cast_as::<PySequence>(py).unwrap();
461+
assert_eq!(vec![1, 2], seq[1..3].extract::<Vec<i32>>().unwrap());
462+
assert_eq!(Vec::<i32>::new(), seq[3..3].extract::<Vec<i32>>().unwrap());
463+
assert_eq!(vec![1, 2], seq[1..].extract::<Vec<i32>>().unwrap());
464+
assert_eq!(Vec::<i32>::new(), seq[3..].extract::<Vec<i32>>().unwrap());
465+
assert_eq!(vec![1, 1, 2], seq[..].extract::<Vec<i32>>().unwrap());
466+
assert_eq!(vec![1, 2], seq[1..=2].extract::<Vec<i32>>().unwrap());
467+
assert_eq!(vec![1, 1], seq[..2].extract::<Vec<i32>>().unwrap());
468+
assert_eq!(vec![1, 1], seq[..=1].extract::<Vec<i32>>().unwrap());
469+
})
470+
}
471+
472+
#[test]
473+
#[should_panic = "range start index 5 out of range for sequence of length 3"]
474+
fn test_seq_index_trait_range_panic_start() {
475+
Python::with_gil(|py| {
476+
let v: Vec<i32> = vec![1, 1, 2];
477+
let ob = v.to_object(py);
478+
let seq = ob.cast_as::<PySequence>(py).unwrap();
479+
seq[5..3].extract::<Vec<i32>>().unwrap();
480+
})
481+
}
482+
483+
#[test]
484+
#[should_panic = "range end index 10 out of range for sequence of length 3"]
485+
fn test_seq_index_trait_range_panic_end() {
486+
Python::with_gil(|py| {
487+
let v: Vec<i32> = vec![1, 1, 2];
488+
let ob = v.to_object(py);
489+
let seq = ob.cast_as::<PySequence>(py).unwrap();
490+
seq[1..10].extract::<Vec<i32>>().unwrap();
491+
})
492+
}
493+
494+
#[test]
495+
#[should_panic = "slice index starts at 2 but ends at 1"]
496+
fn test_seq_index_trait_range_panic_wrong_order() {
497+
Python::with_gil(|py| {
498+
let v: Vec<i32> = vec![1, 1, 2];
499+
let ob = v.to_object(py);
500+
let seq = ob.cast_as::<PySequence>(py).unwrap();
501+
seq[2..1].extract::<Vec<i32>>().unwrap();
502+
})
503+
}
504+
505+
#[test]
506+
#[should_panic = "range start index 8 out of range for sequence of length 3"]
507+
fn test_seq_index_trait_range_from_panic() {
508+
Python::with_gil(|py| {
509+
let v: Vec<i32> = vec![1, 1, 2];
510+
let ob = v.to_object(py);
511+
let seq = ob.cast_as::<PySequence>(py).unwrap();
512+
seq[8..].extract::<Vec<i32>>().unwrap();
513+
})
514+
}
515+
452516
#[test]
453517
fn test_seq_del_item() {
454518
Python::with_gil(|py| {

0 commit comments

Comments
 (0)