1use super::{
2 PyGenericAlias, PyInt, PyIntRef, PySlice, PyTupleRef, PyType, PyTypeRef, builtins_iter,
3 tuple::tuple_hash,
4};
5use crate::common::lock::LazyLock;
6use crate::{
7 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
8 VirtualMachine, atomic_func,
9 class::{PyClassDef, PyClassImpl},
10 common::hash::PyHash,
11 function::{ArgIndex, FuncArgs, OptionalArg, PyComparisonValue},
12 protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods},
13 types::{
14 AsMapping, AsNumber, AsSequence, Comparable, Hashable, IterNext, Iterable, PyComparisonOp,
15 Representable, SelfIter,
16 },
17};
18use core::cell::Cell;
19use core::cmp::max;
20use core::ptr::NonNull;
21use crossbeam_utils::atomic::AtomicCell;
22use malachite_bigint::{BigInt, Sign};
23use num_integer::Integer;
24use num_traits::{One, Signed, ToPrimitive, Zero};
25
26enum SearchType {
28 Count,
29 Contains,
30 Index,
31}
32
33#[inline]
34fn iter_search(
35 obj: &PyObject,
36 item: &PyObject,
37 flag: SearchType,
38 vm: &VirtualMachine,
39) -> PyResult<usize> {
40 let mut count = 0;
41 let iter = obj.get_iter(vm)?;
42 for element in iter.iter::<PyObjectRef>(vm)? {
43 if vm.bool_eq(item, &*element?)? {
44 match flag {
45 SearchType::Index => return Ok(count),
46 SearchType::Contains => return Ok(1),
47 SearchType::Count => count += 1,
48 }
49 }
50 }
51 match flag {
52 SearchType::Count => Ok(count),
53 SearchType::Contains => Ok(0),
54 SearchType::Index => Err(vm.new_value_error(format!(
55 "{} not in range",
56 item.repr(vm)
57 .as_ref()
58 .map_or_else(|_| "value".as_ref(), |s| s.as_wtf8())
59 .to_owned()
60 ))),
61 }
62}
63
64#[pyclass(module = false, name = "range")]
65#[derive(Debug, Clone)]
66pub struct PyRange {
67 #[pymember(type = "object_ex")]
68 pub start: PyIntRef,
69 #[pymember(type = "object_ex")]
70 pub stop: PyIntRef,
71 #[pymember(type = "object_ex")]
72 pub step: PyIntRef,
73}
74
75thread_local! {
77 static RANGE_FREELIST: Cell<crate::object::FreeList<PyRange>> = const { Cell::new(crate::object::FreeList::new()) };
78}
79
80impl PyPayload for PyRange {
81 const MAX_FREELIST: usize = 6;
82 const HAS_FREELIST: bool = true;
83
84 #[inline]
85 fn class(ctx: &Context) -> &'static Py<PyType> {
86 ctx.types.range_type
87 }
88
89 #[inline]
90 unsafe fn freelist_push(obj: *mut PyObject) -> bool {
91 RANGE_FREELIST
92 .try_with(|fl| {
93 let mut list = fl.take();
94 let stored = if list.len() < Self::MAX_FREELIST {
95 list.push(obj);
96 true
97 } else {
98 false
99 };
100 fl.set(list);
101 stored
102 })
103 .unwrap_or(false)
104 }
105
106 #[inline]
107 unsafe fn freelist_pop(_payload: &Self) -> Option<NonNull<PyObject>> {
108 RANGE_FREELIST
109 .try_with(|fl| {
110 let mut list = fl.take();
111 let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
112 fl.set(list);
113 result
114 })
115 .ok()
116 .flatten()
117 }
118}
119
120impl PyRange {
121 #[inline]
122 fn offset(&self, value: &BigInt) -> Option<BigInt> {
123 let start = self.start.as_bigint();
124 let stop = self.stop.as_bigint();
125 let step = self.step.as_bigint();
126 match step.sign() {
127 Sign::Plus if value >= start && value < stop => Some(value - start),
128 Sign::Minus if value <= self.start.as_bigint() && value > stop => Some(start - value),
129 _ => None,
130 }
131 }
132
133 #[inline]
134 #[must_use]
135 pub fn index_of(&self, value: &BigInt) -> Option<BigInt> {
136 let step = self.step.as_bigint();
137 match self.offset(value) {
138 Some(ref offset) if offset.is_multiple_of(step) => Some((offset / step).abs()),
139 Some(_) | None => None,
140 }
141 }
142
143 #[inline]
144 #[must_use]
145 pub fn is_empty(&self) -> bool {
146 self.compute_length().is_zero()
147 }
148
149 #[inline]
150 #[must_use]
151 pub fn forward(&self) -> bool {
152 self.start.as_bigint() < self.stop.as_bigint()
153 }
154
155 #[inline]
156 #[must_use]
157 pub fn get(&self, index: &BigInt) -> Option<BigInt> {
158 let start = self.start.as_bigint();
159 let step = self.step.as_bigint();
160 let stop = self.stop.as_bigint();
161 if self.is_empty() {
162 return None;
163 }
164
165 if index.is_negative() {
166 let length = self.compute_length();
167 let index: BigInt = &length + index;
168 if index.is_negative() {
169 return None;
170 }
171
172 Some(if step.is_one() {
173 start + index
174 } else {
175 start + step * index
176 })
177 } else {
178 let index = if step.is_one() {
179 start + index
180 } else {
181 start + step * index
182 };
183
184 if (step.is_positive() && stop > &index) || (step.is_negative() && stop < &index) {
185 Some(index)
186 } else {
187 None
188 }
189 }
190 }
191
192 #[inline]
193 fn compute_length(&self) -> BigInt {
194 let start = self.start.as_bigint();
195 let stop = self.stop.as_bigint();
196 let step = self.step.as_bigint();
197
198 match step.sign() {
199 Sign::Plus if start < stop => {
200 if step.is_one() {
201 stop - start
202 } else {
203 (stop - start - 1usize) / step + 1
204 }
205 }
206 Sign::Minus if start > stop => (start - stop - 1usize) / (-step) + 1,
207 Sign::Plus | Sign::Minus => BigInt::zero(),
208 Sign::NoSign => unreachable!(),
209 }
210 }
211}
212
213pub(crate) fn init(context: &'static Context) {
218 PyRange::extend_class(context, context.types.range_type);
219 PyLongRangeIterator::extend_class(context, context.types.long_range_iterator_type);
220 PyRangeIterator::extend_class(context, context.types.range_iterator_type);
221}
222
223impl PyRange {
224 fn new(cls: PyTypeRef, stop: ArgIndex, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
225 Self {
226 start: vm.ctx.new_pyref(0),
227 stop: stop.into(),
228 step: vm.ctx.new_pyref(1),
229 }
230 .into_ref_with_type(vm, cls)
231 }
232
233 fn new_from(
234 cls: PyTypeRef,
235 start: &PyObject,
236 stop: &PyObject,
237 step: OptionalArg<ArgIndex>,
238 vm: &VirtualMachine,
239 ) -> PyResult<PyRef<Self>> {
240 let step = step.map_or_else(|| vm.ctx.new_int(1), |step| step.into());
241 if step.as_bigint().is_zero() {
242 return Err(vm.new_value_error("range() arg 3 must not be zero"));
243 }
244 Self {
245 start: start.try_index(vm)?,
246 stop: stop.try_index(vm)?,
247 step,
248 }
249 .into_ref_with_type(vm, cls)
250 }
251
252 fn __len__(&self) -> BigInt {
253 self.compute_length()
254 }
255
256 fn __getitem__(&self, subscript: PyObjectRef, vm: &VirtualMachine) -> PyResult {
257 match RangeIndex::try_from_object(vm, subscript)? {
258 RangeIndex::Slice(slice) => {
259 let (mut sub_start, mut sub_stop, mut sub_step) =
260 slice.inner_indices(&self.compute_length(), vm)?;
261 let range_step = &self.step;
262 let range_start = &self.start;
263
264 sub_step *= range_step.as_bigint();
265 sub_start = (sub_start * range_step.as_bigint()) + range_start.as_bigint();
266 sub_stop = (sub_stop * range_step.as_bigint()) + range_start.as_bigint();
267
268 Ok(Self {
269 start: vm.ctx.new_pyref(sub_start),
270 stop: vm.ctx.new_pyref(sub_stop),
271 step: vm.ctx.new_pyref(sub_step),
272 }
273 .into_ref(&vm.ctx)
274 .into())
275 }
276 RangeIndex::Int(index) => match self.get(index.as_bigint()) {
277 Some(value) => Ok(vm.ctx.new_int(value).into()),
278 None => Err(vm.new_index_error("range object index out of range")),
279 },
280 }
281 }
282
283 fn __class_getitem__(
286 cls: PyTypeRef,
287 args: PyObjectRef,
288 vm: &VirtualMachine,
289 ) -> PyResult<PyGenericAlias> {
290 PyGenericAlias::from_args(cls, args, vm)
291 }
292}
293
294#[pyclass(
295 with(
296 Py,
297 AsMapping,
298 AsNumber,
299 AsSequence,
300 Hashable,
301 Comparable,
302 Iterable,
303 Representable
304 ),
305 flags(SEQUENCE)
306)]
307impl PyRange {}
308
309#[pyclass]
310impl Py<PyRange> {
311 fn contains_inner(&self, needle: &PyObject, vm: &VirtualMachine) -> bool {
312 if let Some(int) = needle.downcast_ref_if_exact::<PyInt>(vm) {
314 match self.offset(int.as_bigint()) {
315 Some(ref offset) => offset.is_multiple_of(self.step.as_bigint()),
316 None => false,
317 }
318 } else {
319 iter_search(self.as_object(), needle, SearchType::Contains, vm).unwrap_or(0) != 0
320 }
321 }
322
323 fn __contains__(&self, needle: &PyObject, vm: &VirtualMachine) -> bool {
324 self.contains_inner(needle, vm)
325 }
326
327 #[pymethod]
328 fn index(&self, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<BigInt> {
329 if let Ok(int) = object.clone().downcast::<PyInt>() {
330 match self.index_of(int.as_bigint()) {
331 Some(idx) => Ok(idx),
332 None => Err(vm.new_value_error(format!("{int} is not in range"))),
333 }
334 } else {
335 Ok(BigInt::from_bytes_be(
337 Sign::Plus,
338 &iter_search(self.as_object(), &object, SearchType::Index, vm)?.to_be_bytes(),
339 ))
340 }
341 }
342
343 #[pymethod]
344 fn count(&self, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> {
345 if let Ok(int) = object.clone().downcast::<PyInt>() {
346 Ok(usize::from(self.index_of(int.as_bigint()).is_some()))
347 } else {
348 iter_search(self.as_object(), &object, SearchType::Count, vm)
351 }
352 }
353
354 #[pymethod]
355 fn __reversed__(&self, vm: &VirtualMachine) -> PyObjectRef {
356 let start = self.start.as_bigint();
357 let step = self.step.as_bigint();
358
359 let length = self.__len__();
361 let new_stop = start - step;
362 let start = &new_stop + length.clone() * step;
363 let step = -step;
364
365 if let (Some(start), Some(step), Some(_)) =
366 (start.to_isize(), step.to_isize(), new_stop.to_isize())
367 {
368 PyRangeIterator {
369 index: AtomicCell::new(0),
370 start,
371 step,
372 length: length.to_usize().unwrap_or(0),
375 }
376 .into_pyobject(vm)
377 } else {
378 PyLongRangeIterator {
379 index: AtomicCell::new(0),
380 start,
381 step,
382 length,
383 }
384 .into_pyobject(vm)
385 }
386 }
387
388 #[pymethod]
389 fn __reduce__(&self, vm: &VirtualMachine) -> (PyTypeRef, PyTupleRef) {
390 let range_parameters: Vec<PyObjectRef> = [&self.start, &self.stop, &self.step]
391 .iter()
392 .map(|x| x.as_object().to_owned())
393 .collect();
394 let range_parameters_tuple = vm.ctx.new_tuple(range_parameters);
395 (vm.ctx.types.range_type.to_owned(), range_parameters_tuple)
396 }
397
398 #[pyslot]
399 fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
400 let range = if args.args.is_empty() {
401 return Err(vm.new_arity_type_error(PyRange::NAME, 1..=3, 0));
402 } else if args.args.len() == 1 {
403 let stop = args.bind_for(vm, PyRange::NAME)?;
404 PyRange::new(cls, stop, vm)
405 } else {
406 let (start, stop, step): (PyObjectRef, PyObjectRef, OptionalArg<ArgIndex>) =
407 args.bind_for(vm, PyRange::NAME)?;
408 PyRange::new_from(cls, &start, &stop, step, vm)
409 }?;
410
411 Ok(range.into())
412 }
413}
414
415impl PyRange {
416 fn protocol_length(&self, vm: &VirtualMachine) -> PyResult<usize> {
417 self.__len__()
418 .to_isize()
419 .map(|x| x as usize)
420 .ok_or_else(|| vm.new_overflow_error("Python int too large to convert to Rust isize"))
421 }
422}
423
424impl AsMapping for PyRange {
425 fn as_mapping() -> &'static PyMappingMethods {
426 static AS_MAPPING: LazyLock<PyMappingMethods> = LazyLock::new(|| PyMappingMethods {
427 length: atomic_func!(
428 |mapping, vm| PyRange::mapping_downcast(mapping).protocol_length(vm)
429 ),
430 subscript: atomic_func!(|mapping, needle, vm| {
431 PyRange::mapping_downcast(mapping).__getitem__(needle.to_owned(), vm)
432 }),
433 ..PyMappingMethods::NOT_IMPLEMENTED
434 });
435 &AS_MAPPING
436 }
437}
438
439impl AsSequence for PyRange {
440 fn as_sequence() -> &'static PySequenceMethods {
441 static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods {
442 length: atomic_func!(|seq, vm| PyRange::sequence_downcast(seq).protocol_length(vm)),
443 item: atomic_func!(|seq, i, vm| {
444 PyRange::sequence_downcast(seq)
445 .get(&i.into())
446 .map(|x| PyInt::from(x).into_ref(&vm.ctx).into())
447 .ok_or_else(|| vm.new_index_error("index out of range"))
448 }),
449 contains: atomic_func!(|seq, needle, vm| {
450 Ok(PyRange::sequence_downcast(seq).contains_inner(needle, vm))
451 }),
452 ..PySequenceMethods::NOT_IMPLEMENTED
453 });
454 &AS_SEQUENCE
455 }
456}
457
458impl AsNumber for PyRange {
459 fn as_number() -> &'static PyNumberMethods {
460 static AS_NUMBER: PyNumberMethods = PyNumberMethods {
461 boolean: Some(|number, _vm| {
462 let zelf = number.obj.downcast_ref::<PyRange>().unwrap();
463 Ok(!zelf.is_empty())
464 }),
465 ..PyNumberMethods::NOT_IMPLEMENTED
466 };
467 &AS_NUMBER
468 }
469}
470
471impl Hashable for PyRange {
472 fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
473 let length = zelf.compute_length();
474 let elements = if length.is_zero() {
475 [vm.ctx.new_int(length).into(), vm.ctx.none(), vm.ctx.none()]
476 } else if length.is_one() {
477 [
478 vm.ctx.new_int(length).into(),
479 zelf.start.clone().into(),
480 vm.ctx.none(),
481 ]
482 } else {
483 [
484 vm.ctx.new_int(length).into(),
485 zelf.start.clone().into(),
486 zelf.step.clone().into(),
487 ]
488 };
489 tuple_hash(&elements, vm)
490 }
491}
492
493impl Comparable for PyRange {
494 fn cmp(
495 zelf: &Py<Self>,
496 other: &PyObject,
497 op: PyComparisonOp,
498 _vm: &VirtualMachine,
499 ) -> PyResult<PyComparisonValue> {
500 op.eq_only(|| {
501 if zelf.is(other) {
502 return Ok(true.into());
503 }
504 let rhs = class_or_notimplemented!(Self, other);
505 let lhs_len = zelf.compute_length();
506 let eq = if lhs_len != rhs.compute_length() {
507 false
508 } else if lhs_len.is_zero() {
509 true
510 } else if zelf.start.as_bigint() != rhs.start.as_bigint() {
511 false
512 } else if lhs_len.is_one() {
513 true
514 } else {
515 zelf.step.as_bigint() == rhs.step.as_bigint()
516 };
517 Ok(eq.into())
518 })
519 }
520}
521
522impl Iterable for PyRange {
523 fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
524 let (start, stop, step, length) = (
525 zelf.start.as_bigint(),
526 zelf.stop.as_bigint(),
527 zelf.step.as_bigint(),
528 zelf.__len__(),
529 );
530 if let (Some(start), Some(step), Some(_), Some(_)) = (
531 start.to_isize(),
532 step.to_isize(),
533 stop.to_isize(),
534 (start + step).to_isize(),
535 ) {
536 Ok(PyRangeIterator {
537 index: AtomicCell::new(0),
538 start,
539 step,
540 length: length.to_usize().unwrap_or(0),
543 }
544 .into_pyobject(vm))
545 } else {
546 Ok(PyLongRangeIterator {
547 index: AtomicCell::new(0),
548 start: start.clone(),
549 step: step.clone(),
550 length,
551 }
552 .into_pyobject(vm))
553 }
554 }
555}
556
557impl Representable for PyRange {
558 #[inline]
559 fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
560 let repr = if zelf.step.as_bigint().is_one() {
561 format!("range({}, {})", zelf.start, zelf.stop)
562 } else {
563 format!("range({}, {}, {})", zelf.start, zelf.stop, zelf.step)
564 };
565 Ok(repr)
566 }
567}
568
569#[pyclass(module = false, name = "longrange_iterator")]
578#[derive(Debug)]
579pub(crate) struct PyLongRangeIterator {
580 index: AtomicCell<usize>,
581 start: BigInt,
582 step: BigInt,
583 length: BigInt,
584}
585
586impl PyPayload for PyLongRangeIterator {
587 #[inline]
588 fn class(ctx: &Context) -> &'static Py<PyType> {
589 ctx.types.long_range_iterator_type
590 }
591}
592
593#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))]
594impl Py<PyLongRangeIterator> {
595 #[pymethod]
596 fn __length_hint__(&self) -> BigInt {
597 let index = BigInt::from(self.index.load());
598 if index < self.length {
599 self.length.clone() - index
600 } else {
601 BigInt::zero()
602 }
603 }
604
605 #[pymethod]
606 fn __setstate__(&self, state: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
607 self.index.store(range_state(&self.length, &state, vm)?);
608 Ok(())
609 }
610
611 #[pymethod]
612 fn __reduce__(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
613 range_iter_reduce(
614 self.start.clone(),
615 self.length.clone(),
616 self.step.clone(),
617 self.index.load(),
618 vm,
619 )
620 }
621}
622
623impl SelfIter for PyLongRangeIterator {}
624impl IterNext for PyLongRangeIterator {
625 fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
626 let index = BigInt::from(zelf.index.fetch_add(1));
630 let r = if index < zelf.length {
631 let value = zelf.start.clone() + index * zelf.step.clone();
632 PyIterReturn::Return(vm.ctx.new_int(value).into())
633 } else {
634 PyIterReturn::StopIteration(None)
635 };
636 Ok(r)
637 }
638}
639
640#[pyclass(module = false, name = "range_iterator")]
643#[derive(Debug)]
644pub(crate) struct PyRangeIterator {
645 index: AtomicCell<usize>,
646 start: isize,
647 step: isize,
648 length: usize,
649}
650
651impl PyPayload for PyRangeIterator {
652 #[inline]
653 fn class(ctx: &Context) -> &'static Py<PyType> {
654 ctx.types.range_iterator_type
655 }
656}
657
658#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))]
659impl Py<PyRangeIterator> {
660 #[pymethod]
661 fn __length_hint__(&self) -> usize {
662 let index = self.index.load();
663 self.length.saturating_sub(index)
664 }
665
666 #[pymethod]
667 fn __setstate__(&self, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
668 self.index
669 .store(range_state(&BigInt::from(self.length), &object, vm)?);
670 Ok(())
671 }
672
673 #[pymethod]
674 fn __reduce__(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
675 range_iter_reduce(
676 BigInt::from(self.start),
677 BigInt::from(self.length),
678 BigInt::from(self.step),
679 self.index.load(),
680 vm,
681 )
682 }
683}
684
685impl PyRangeIterator {
686 pub(crate) fn fast_next(&self) -> Option<isize> {
689 let index = self.index.fetch_add(1);
690 if index < self.length {
691 Some(self.start + (index as isize) * self.step)
692 } else {
693 None
694 }
695 }
696}
697
698impl SelfIter for PyRangeIterator {}
699impl IterNext for PyRangeIterator {
700 fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
701 let r = match zelf.fast_next() {
702 Some(value) => PyIterReturn::Return(vm.ctx.new_int(value).into()),
703 None => PyIterReturn::StopIteration(None),
704 };
705 Ok(r)
706 }
707}
708
709fn range_iter_reduce(
710 start: BigInt,
711 length: BigInt,
712 step: BigInt,
713 index: usize,
714 vm: &VirtualMachine,
715) -> PyResult<PyTupleRef> {
716 let iter = builtins_iter(vm)?;
717 let index = BigInt::from(index).min(length.clone());
720 let stop = start.clone() + length * step.clone();
721 let start = start + index * step.clone();
722 let range = PyRange {
723 start: PyInt::from(start).into_ref(&vm.ctx),
724 stop: PyInt::from(stop).into_ref(&vm.ctx),
725 step: PyInt::from(step).into_ref(&vm.ctx),
726 };
727 Ok(vm.new_tuple((iter, (range,), vm.ctx.none())))
728}
729
730fn range_state(length: &BigInt, state: &PyObject, vm: &VirtualMachine) -> PyResult<usize> {
732 if let Some(i) = state.downcast_ref::<PyInt>() {
733 let mut index = i.as_bigint();
734 let max_usize = BigInt::from(usize::MAX);
735 if index > length {
736 index = max(length, &max_usize);
737 }
738 Ok(index.to_usize().unwrap_or(0))
739 } else {
740 Err(vm.new_type_error("an integer is required."))
741 }
742}
743
744pub(crate) enum RangeIndex {
745 Int(PyIntRef),
746 Slice(PyRef<PySlice>),
747}
748
749impl TryFromObject for RangeIndex {
750 fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
751 match_class!(match obj {
752 i @ PyInt => Ok(Self::Int(i)),
753 s @ PySlice => Ok(Self::Slice(s)),
754 obj => {
755 let val = obj.try_index(vm).map_err(|_| vm.new_type_error(format!(
756 "sequence indices be integers or slices or classes that override __index__ operator, not '{}'",
757 obj.class().name()
758 )))?;
759 Ok(Self::Int(val))
760 }
761 })
762 }
763}