Skip to main content

rustpython_vm/builtins/
range.rs

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
26// Search flag passed to iter_search
27enum 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
75// spell-checker:ignore MAXFREELIST
76thread_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
213// pub fn get_value(obj: &PyObject) -> PyRange {
214//     obj.downcast_ref::<PyRange>().unwrap().clone()
215// }
216
217pub(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    // TODO: Uncomment when Python adds __class_getitem__ to range
284    // #[pyclassmethod]
285    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        // Only accept ints, not subclasses.
313        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            // Fallback to iteration.
336            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            // Dealing with classes who might compare equal with ints in their
349            // __eq__, slow search.
350            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        // Use CPython calculation for this:
360        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                // Cannot fail. If start, stop and step all successfully convert to isize, then result of zelf.len will
373                // always fit in a usize.
374                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                // Cannot fail. If start, stop and step all successfully convert to isize, then result of zelf.len will
541                // always fit in a usize.
542                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// Semantically, this is the same as the previous representation.
570//
571// Unfortunately, since AtomicCell requires a Copy type, no BigInt implementations can
572// generally be used. As such, usize::MAX is the upper bound on number of elements (length)
573// the range can contain in RustPython.
574//
575// This doesn't preclude the range from containing large values, since start and step
576// can be BigInts, we can store any arbitrary range of values.
577#[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        // TODO: In pathological case (index == usize::MAX) this can wrap around
627        // (since fetch_add wraps). This would result in the iterator spinning again
628        // from the beginning.
629        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// When start, stop, step are isize, we can use a faster more compact representation
641// that only operates using isize to track values.
642#[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    /// Fast path for FOR_ITER specialization. Returns the next isize value
687    /// without allocating PyInt or PyIterReturn.
688    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    // CPython pickles the remaining range with a None state. next() increments
718    // the index unconditionally, so clamp it to length before rebasing start.
719    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
730// Silently clips state (i.e index) in range [0, usize::MAX].
731fn 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}