Skip to main content

rustpython_vm/builtins/
bytes.rs

1use super::{
2    PositionIterInternal, PyDictRef, PyGenericAlias, PyStrRef, PyTuple, PyTupleRef, PyType,
3    PyTypeRef, iter::builtins_iter, locked_next,
4};
5use crate::common::lock::LazyLock;
6use crate::{
7    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
8    TryFromBorrowedObject, VirtualMachine,
9    anystr::{self, AnyStr},
10    atomic_func,
11    byte::bytes_from_object,
12    bytes_inner::{
13        ByteInnerFindOptions, ByteInnerHexOptions, ByteInnerNewOptions, ByteInnerPaddingOptions,
14        ByteInnerReplaceOptions, ByteInnerSplitOptions, ByteInnerStripOptions, ByteInnerSub,
15        ByteInnerTranslateOptions, BytesJoin, DecodeArgs, PyBytesInner, bytes_decode,
16    },
17    class::{PyClassDef, PyClassImpl},
18    common::{hash::PyHash, lock::PyMutex},
19    convert::{ToPyObject, ToPyResult},
20    function::{ArgBytesLike, ArgIndex, FuncArgs, OptionalArg, PyComparisonValue},
21    protocol::{
22        BufferDescriptor, BufferFlags, BufferMethods, PyBuffer, PyIterReturn, PyMappingMethods,
23        PyNumberMethods, PySequenceMethods,
24    },
25    sliceable::{SequenceIndex, SliceableSequenceOp},
26    types::{
27        AsBuffer, AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable,
28        IterNext, Iterable, PyComparisonOp, Representable, SelfIter,
29    },
30};
31use bstr::ByteSlice;
32use core::{mem::size_of, ops::Deref};
33use memchr::memchr;
34
35#[pyclass(module = false, name = "bytes")]
36#[derive(Clone, Debug)]
37pub struct PyBytes {
38    inner: PyBytesInner,
39}
40
41pub type PyBytesRef = PyRef<PyBytes>;
42
43impl From<Vec<u8>> for PyBytes {
44    fn from(elements: Vec<u8>) -> Self {
45        Self {
46            inner: PyBytesInner { elements },
47        }
48    }
49}
50
51impl From<PyBytesInner> for PyBytes {
52    fn from(inner: PyBytesInner) -> Self {
53        Self { inner }
54    }
55}
56
57impl ToPyObject for Vec<u8> {
58    fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
59        vm.ctx.new_bytes(self).into()
60    }
61}
62
63impl<const N: usize> ToPyObject for &[u8; N] {
64    fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
65        vm.ctx.new_bytes(self.as_slice().to_vec()).into()
66    }
67}
68
69impl Deref for PyBytes {
70    type Target = [u8];
71
72    fn deref(&self) -> &[u8] {
73        self.as_bytes()
74    }
75}
76
77impl AsRef<[u8]> for PyBytes {
78    fn as_ref(&self) -> &[u8] {
79        self.as_bytes()
80    }
81}
82impl AsRef<[u8]> for PyBytesRef {
83    fn as_ref(&self) -> &[u8] {
84        self.as_bytes()
85    }
86}
87
88impl PyPayload for PyBytes {
89    #[inline]
90    fn class(ctx: &Context) -> &'static Py<PyType> {
91        ctx.types.bytes_type
92    }
93}
94
95pub(crate) fn init(context: &'static Context) {
96    PyBytes::extend_class(context, context.types.bytes_type);
97    PyBytesIterator::extend_class(context, context.types.bytes_iterator_type);
98}
99
100impl Constructor for PyBytes {
101    type Args = Vec<u8>;
102
103    fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
104        let options: ByteInnerNewOptions = args.bind_for(vm, Self::NAME)?;
105
106        // Optimizations for exact bytes type
107        if cls.is(vm.ctx.types.bytes_type) {
108            // Return empty bytes singleton
109            if options.source.is_missing()
110                && options.encoding.is_missing()
111                && options.errors.is_missing()
112            {
113                return Ok(vm.ctx.empty_bytes.clone().into());
114            }
115
116            // Return exact bytes as-is
117            if let OptionalArg::Present(ref obj) = options.source
118                && options.encoding.is_missing()
119                && options.errors.is_missing()
120                && let Ok(b) = obj.clone().downcast_exact::<Self>(vm)
121            {
122                return Ok(b.into_pyref().into());
123            }
124        }
125
126        // Handle __bytes__ method - may return PyBytes directly
127        if let OptionalArg::Present(ref obj) = options.source
128            && options.encoding.is_missing()
129            && options.errors.is_missing()
130            && let Some(bytes_method) = vm.get_method(obj.clone(), identifier!(vm, __bytes__))
131        {
132            let bytes = bytes_method?.call((), vm)?;
133            // If exact bytes type and __bytes__ returns bytes, use it directly
134            if cls.is(vm.ctx.types.bytes_type)
135                && let Ok(b) = bytes.clone().downcast::<Self>()
136            {
137                return Ok(b.into());
138            }
139            // Otherwise convert to Vec<u8>
140            let inner = PyBytesInner::try_from_borrowed_object(vm, &bytes)?;
141            let payload = Self::py_new(&cls, inner.elements, vm)?;
142            return payload.into_ref_with_type(vm, cls).map(Into::into);
143        }
144
145        let elements = options.get_inner(bytes_from_object, vm)?.elements;
146
147        // Return empty bytes singleton for exact bytes types
148        if elements.is_empty() && cls.is(vm.ctx.types.bytes_type) {
149            return Ok(vm.ctx.empty_bytes.clone().into());
150        }
151
152        let payload = Self::py_new(&cls, elements, vm)?;
153        payload.into_ref_with_type(vm, cls).map(Into::into)
154    }
155
156    fn py_new(_cls: &Py<PyType>, elements: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
157        Ok(Self::from(elements))
158    }
159}
160
161impl PyBytes {
162    #[deprecated(note = "use PyBytes::from(...).into_ref() instead")]
163    pub fn new_ref(data: Vec<u8>, ctx: &Context) -> PyRef<Self> {
164        Self::from(data).into_ref(ctx)
165    }
166
167    fn _getitem(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult {
168        match SequenceIndex::try_from_borrowed_object(vm, needle, "byte")? {
169            SequenceIndex::Int(i) => self
170                .getitem_by_index(vm, i)
171                .map(|x| vm.ctx.new_int(x).into()),
172            SequenceIndex::Slice(slice) => self
173                .getitem_by_slice(vm, slice)
174                .map(|x| vm.ctx.new_bytes(x).into()),
175        }
176    }
177
178    /// Check bytes for interior NULs.
179    #[inline]
180    #[must_use]
181    pub fn contains_nuls(&self) -> bool {
182        memchr(b'\0', self.as_bytes()).is_some()
183    }
184}
185
186impl PyRef<PyBytes> {
187    fn repeat(self, count: isize, vm: &VirtualMachine) -> PyResult<Self> {
188        if count == 1 && self.class().is(vm.ctx.types.bytes_type) {
189            // Special case: when some `bytes` is multiplied by `1`,
190            // nothing really happens, we need to return an object itself
191            // with the same `id()` to be compatible with CPython.
192            // This only works for `bytes` itself, not its subclasses.
193            return Ok(self);
194        }
195        self.payload
196            .inner
197            .mul(count, vm)
198            .map(|x| PyBytes::from(x).into_ref(&vm.ctx))
199    }
200}
201
202impl PyBytes {
203    #[inline]
204    #[must_use]
205    pub const fn __len__(&self) -> usize {
206        self.inner.len()
207    }
208
209    #[inline]
210    #[must_use]
211    pub const fn is_empty(&self) -> bool {
212        self.inner.is_empty()
213    }
214
215    #[inline]
216    #[must_use]
217    pub const fn as_bytes(&self) -> &[u8] {
218        self.inner.as_bytes()
219    }
220
221    fn __mul__(zelf: PyRef<Self>, value: ArgIndex, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
222        zelf.repeat(value.into_int_ref().try_to_primitive(vm)?, vm)
223    }
224
225    // TODO: Uncomment when Python adds __class_getitem__ to bytes
226    // #[pyclassmethod]
227    fn __class_getitem__(
228        cls: PyTypeRef,
229        args: PyObjectRef,
230        vm: &VirtualMachine,
231    ) -> PyResult<PyGenericAlias> {
232        PyGenericAlias::from_args(cls, args, vm)
233    }
234}
235
236#[pyclass(
237    itemsize = 1,
238    flags(BASETYPE, _MATCH_SELF),
239    with(
240        Py,
241        PyRef,
242        AsMapping,
243        AsSequence,
244        Hashable,
245        Comparable,
246        AsBuffer,
247        Iterable,
248        Constructor,
249        AsNumber,
250        Representable,
251    )
252)]
253impl PyBytes {}
254
255#[pyclass]
256impl Py<PyBytes> {
257    #[inline]
258    pub fn as_bytes(&self) -> &[u8] {
259        self.payload().as_bytes()
260    }
261
262    fn __add__(&self, other: ArgBytesLike) -> Vec<u8> {
263        self.payload.inner.add(&other.borrow_buf())
264    }
265
266    fn __contains__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
267        let needle = ByteInnerSub::from_contains_arg(needle, vm)?;
268        self.payload.inner.contains(needle, vm)
269    }
270
271    fn __getitem__(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult {
272        self.payload._getitem(needle, vm)
273    }
274
275    fn __mod__(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyBytes> {
276        let formatted = self.payload.inner.cformat(values, vm)?;
277        Ok(formatted.into())
278    }
279
280    #[pymethod]
281    fn __reduce_ex__(
282        &self,
283        _proto: usize,
284        vm: &VirtualMachine,
285    ) -> (PyTypeRef, PyTupleRef, Option<PyDictRef>) {
286        self.__reduce__(vm)
287    }
288
289    #[pymethod]
290    fn __reduce__(&self, vm: &VirtualMachine) -> (PyTypeRef, PyTupleRef, Option<PyDictRef>) {
291        let bytes = PyBytes::from(self.as_bytes().to_vec()).to_pyobject(vm);
292        (
293            self.class().to_owned(),
294            PyTuple::new_ref(vec![bytes], &vm.ctx),
295            self.as_object().dict(),
296        )
297    }
298
299    #[pymethod]
300    fn __sizeof__(&self) -> usize {
301        size_of::<PyBytes>() + self.len() * size_of::<u8>()
302    }
303
304    #[pyslot]
305    fn slot_str(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
306        let zelf = zelf.downcast_ref::<PyBytes>().expect("expected bytes");
307        PyBytesInner::warn_on_str("str() on a bytes instance", vm)?;
308        Ok(vm.ctx.new_str(zelf.payload.inner.repr_bytes(vm)?))
309    }
310
311    #[pystaticmethod]
312    fn maketrans(frm: PyBytesInner, to: PyBytesInner, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
313        PyBytesInner::maketrans(frm, to, vm)
314    }
315
316    #[pymethod]
317    fn isalnum(&self) -> bool {
318        self.inner.isalnum()
319    }
320
321    #[pymethod]
322    fn isalpha(&self) -> bool {
323        self.inner.isalpha()
324    }
325
326    #[pymethod]
327    fn isascii(&self) -> bool {
328        self.inner.isascii()
329    }
330
331    #[pymethod]
332    fn isdigit(&self) -> bool {
333        self.inner.isdigit()
334    }
335
336    #[pymethod]
337    fn islower(&self) -> bool {
338        self.inner.islower()
339    }
340
341    #[pymethod]
342    fn isspace(&self) -> bool {
343        self.inner.isspace()
344    }
345
346    #[pymethod]
347    fn isupper(&self) -> bool {
348        self.inner.isupper()
349    }
350
351    #[pymethod]
352    fn istitle(&self) -> bool {
353        self.inner.istitle()
354    }
355
356    #[pymethod]
357    fn lower(&self) -> PyBytes {
358        self.inner.lower().into()
359    }
360
361    #[pymethod]
362    fn upper(&self) -> PyBytes {
363        self.inner.upper().into()
364    }
365
366    #[pymethod]
367    fn capitalize(&self) -> PyBytes {
368        self.inner.capitalize().into()
369    }
370
371    #[pymethod]
372    fn swapcase(&self) -> PyBytes {
373        self.inner.swapcase().into()
374    }
375
376    #[pymethod]
377    pub(crate) fn hex(
378        &self,
379        options: ByteInnerHexOptions,
380        vm: &VirtualMachine,
381    ) -> PyResult<String> {
382        let (sep, bytes_per_sep) = options.resolve(vm)?;
383        Ok(self.inner.hex(sep, bytes_per_sep))
384    }
385
386    #[pyclassmethod]
387    fn fromhex(cls: PyTypeRef, string: PyObjectRef, vm: &VirtualMachine) -> PyResult {
388        let bytes = PyBytesInner::fromhex_object(&string, vm)?;
389        let bytes = vm.ctx.new_bytes(bytes).into();
390        PyType::call(&cls, vec![bytes].into(), vm)
391    }
392
393    #[pymethod]
394    fn center(&self, options: ByteInnerPaddingOptions, vm: &VirtualMachine) -> PyResult<PyBytes> {
395        Ok(self.inner.center(options, vm)?.into())
396    }
397
398    #[pymethod]
399    fn ljust(&self, options: ByteInnerPaddingOptions, vm: &VirtualMachine) -> PyResult<PyBytes> {
400        Ok(self.inner.ljust(options, vm)?.into())
401    }
402
403    #[pymethod]
404    fn rjust(&self, options: ByteInnerPaddingOptions, vm: &VirtualMachine) -> PyResult<PyBytes> {
405        Ok(self.inner.rjust(options, vm)?.into())
406    }
407
408    #[pymethod]
409    fn count(&self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult<usize> {
410        self.inner.count(options, vm)
411    }
412
413    #[pymethod]
414    fn join(&self, iterable_of_bytes: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
415        let items = BytesJoin::new(iterable_of_bytes, vm)?;
416        if let Some(only) = items.single_bytes(vm) {
417            return Ok(only);
418        }
419        Ok(vm
420            .ctx
421            .new_bytes(items.join(self.as_bytes().len(), || self.as_bytes().into(), vm)?))
422    }
423
424    #[pymethod]
425    fn endswith(&self, options: anystr::StartsEndsWithArgs, vm: &VirtualMachine) -> PyResult<bool> {
426        let (affix, substr) =
427            match options.prepare(self.as_bytes(), self.len(), |s, r| s.get_bytes(r)) {
428                Some(x) => x,
429                None => return Ok(false),
430            };
431        substr.py_starts_ends_with(
432            &affix,
433            "endswith",
434            "bytes",
435            |s, x: PyBytesInner| s.ends_with(x.as_bytes()),
436            vm,
437        )
438    }
439
440    #[pymethod]
441    fn startswith(
442        &self,
443        options: anystr::StartsEndsWithArgs,
444        vm: &VirtualMachine,
445    ) -> PyResult<bool> {
446        let (affix, substr) =
447            match options.prepare(self.as_bytes(), self.len(), |s, r| s.get_bytes(r)) {
448                Some(x) => x,
449                None => return Ok(false),
450            };
451        substr.py_starts_ends_with(
452            &affix,
453            "startswith",
454            "bytes",
455            |s, x: PyBytesInner| s.starts_with(x.as_bytes()),
456            vm,
457        )
458    }
459
460    #[pymethod]
461    fn find(&self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult<isize> {
462        let index = self.inner.find(options, |h, n| h.find(n), vm)?;
463        Ok(index.map_or(-1, |v| v as isize))
464    }
465
466    #[pymethod]
467    fn index(&self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult<usize> {
468        let index = self.inner.find(options, |h, n| h.find(n), vm)?;
469        index.ok_or_else(|| vm.new_value_error("substring not found"))
470    }
471
472    #[pymethod]
473    fn rfind(&self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult<isize> {
474        let index = self.inner.find(options, |h, n| h.rfind(n), vm)?;
475        Ok(index.map_or(-1, |v| v as isize))
476    }
477
478    #[pymethod]
479    fn rindex(&self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult<usize> {
480        let index = self.inner.find(options, |h, n| h.rfind(n), vm)?;
481        index.ok_or_else(|| vm.new_value_error("substring not found"))
482    }
483
484    #[pymethod]
485    fn translate(
486        &self,
487        options: ByteInnerTranslateOptions,
488        vm: &VirtualMachine,
489    ) -> PyResult<PyBytes> {
490        Ok(self.inner.translate(options, vm)?.into())
491    }
492
493    #[pymethod]
494    fn strip(&self, options: ByteInnerStripOptions) -> PyBytes {
495        self.inner.strip(options.bytes).into()
496    }
497
498    #[pymethod]
499    fn removeprefix(&self, prefix: PyBytesInner) -> PyBytes {
500        self.inner.removeprefix(prefix).into()
501    }
502
503    #[pymethod]
504    fn removesuffix(&self, suffix: PyBytesInner) -> PyBytes {
505        self.inner.removesuffix(suffix).into()
506    }
507
508    #[pymethod]
509    fn split(
510        &self,
511        options: ByteInnerSplitOptions,
512        vm: &VirtualMachine,
513    ) -> PyResult<Vec<PyObjectRef>> {
514        self.inner
515            .split(options, |s, vm| vm.ctx.new_bytes(s.to_vec()).into(), vm)
516    }
517
518    #[pymethod]
519    fn rsplit(
520        &self,
521        options: ByteInnerSplitOptions,
522        vm: &VirtualMachine,
523    ) -> PyResult<Vec<PyObjectRef>> {
524        self.inner
525            .rsplit(options, |s, vm| vm.ctx.new_bytes(s.to_vec()).into(), vm)
526    }
527
528    #[pymethod]
529    fn partition(&self, sep: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
530        let sub = PyBytesInner::try_from_borrowed_object(vm, &sep)?;
531        let (front, has_mid, back) = self.inner.partition(&sub, vm)?;
532        Ok(vm.new_tuple((
533            vm.ctx.new_bytes(front),
534            if has_mid {
535                sep
536            } else {
537                vm.ctx.new_bytes(Vec::new()).into()
538            },
539            vm.ctx.new_bytes(back),
540        )))
541    }
542
543    #[pymethod]
544    fn rpartition(&self, sep: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
545        let sub = PyBytesInner::try_from_borrowed_object(vm, &sep)?;
546        let (back, has_mid, front) = self.inner.rpartition(&sub, vm)?;
547        Ok(vm.new_tuple((
548            vm.ctx.new_bytes(front),
549            if has_mid {
550                sep
551            } else {
552                vm.ctx.new_bytes(Vec::new()).into()
553            },
554            vm.ctx.new_bytes(back),
555        )))
556    }
557
558    #[pymethod]
559    fn expandtabs(&self, options: anystr::ExpandTabsArgs) -> PyBytes {
560        self.inner.expandtabs(options).into()
561    }
562
563    #[pymethod]
564    fn splitlines(&self, options: anystr::SplitLinesArgs, vm: &VirtualMachine) -> Vec<PyObjectRef> {
565        self.inner
566            .splitlines(options, |x| vm.ctx.new_bytes(x.to_vec()).into())
567    }
568
569    #[pymethod]
570    fn zfill(&self, width: isize, vm: &VirtualMachine) -> PyResult<PyBytes> {
571        Ok(self.inner.zfill(width, vm)?.into())
572    }
573
574    #[pymethod]
575    fn replace(&self, options: ByteInnerReplaceOptions, vm: &VirtualMachine) -> PyResult<PyBytes> {
576        Ok(self.inner.replace(options, vm)?.into())
577    }
578
579    #[pymethod]
580    fn title(&self) -> PyBytes {
581        self.inner.title().into()
582    }
583
584    #[pymethod]
585    fn __getnewargs__(&self, vm: &VirtualMachine) -> PyTupleRef {
586        let param: Vec<PyObjectRef> = self.elements().map(|x| x.to_pyobject(vm)).collect();
587        PyTuple::new_ref(param, &vm.ctx)
588    }
589}
590
591#[pyclass]
592impl PyRef<PyBytes> {
593    #[pymethod]
594    fn __bytes__(self, vm: &VirtualMachine) -> Self {
595        if self.is(vm.ctx.types.bytes_type) {
596            self
597        } else {
598            PyBytes::from(self.payload.inner.clone()).into_ref(&vm.ctx)
599        }
600    }
601
602    #[pymethod]
603    fn lstrip(self, options: ByteInnerStripOptions, vm: &VirtualMachine) -> Self {
604        let stripped = self.payload.inner.lstrip(options.bytes);
605        if stripped == self.as_bytes() {
606            self
607        } else {
608            vm.ctx.new_bytes(stripped.to_vec())
609        }
610    }
611
612    #[pymethod]
613    fn rstrip(self, options: ByteInnerStripOptions, vm: &VirtualMachine) -> Self {
614        let stripped = self.payload.inner.rstrip(options.bytes);
615        if stripped == self.as_bytes() {
616            self
617        } else {
618            vm.ctx.new_bytes(stripped.to_vec())
619        }
620    }
621
622    // currently, only 'utf-8' and 'ascii' implemented
623    #[pymethod]
624    fn decode(self, args: DecodeArgs, vm: &VirtualMachine) -> PyResult<PyStrRef> {
625        bytes_decode(self.into(), args, vm)
626    }
627}
628
629static BUFFER_METHODS: BufferMethods = BufferMethods {
630    obj_bytes: |buffer| buffer.obj_as::<PyBytes>().as_bytes().into(),
631    obj_bytes_mut: |_| panic!(),
632    release: |_| {},
633    retain: |_| {},
634};
635
636impl AsBuffer for PyBytes {
637    fn slot_as_buffer(
638        zelf: &PyObject,
639        flags: BufferFlags,
640        vm: &VirtualMachine,
641    ) -> PyResult<PyBuffer> {
642        let zelf = zelf
643            .downcast_ref::<Self>()
644            .ok_or_else(|| vm.new_type_error("unexpected payload for as_buffer"))?;
645        flags.fill_info_check(true, vm)?;
646        Self::as_buffer(zelf, vm)
647    }
648
649    fn as_buffer(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyBuffer> {
650        let buf = PyBuffer::new(
651            zelf.to_owned().into(),
652            BufferDescriptor::simple(zelf.as_bytes().len(), true),
653            &BUFFER_METHODS,
654        );
655        Ok(buf)
656    }
657}
658
659impl AsMapping for PyBytes {
660    fn as_mapping() -> &'static PyMappingMethods {
661        static AS_MAPPING: LazyLock<PyMappingMethods> = LazyLock::new(|| PyMappingMethods {
662            length: atomic_func!(|mapping, _vm| {
663                Ok(PyBytes::mapping_downcast(mapping).as_bytes().len())
664            }),
665            subscript: atomic_func!(
666                |mapping, needle, vm| PyBytes::mapping_downcast(mapping).__getitem__(needle, vm)
667            ),
668            ..PyMappingMethods::NOT_IMPLEMENTED
669        });
670        &AS_MAPPING
671    }
672}
673
674impl AsSequence for PyBytes {
675    fn as_sequence() -> &'static PySequenceMethods {
676        static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods {
677            length: atomic_func!(|seq, _vm| Ok(PyBytes::sequence_downcast(seq).as_bytes().len())),
678            concat: atomic_func!(|seq, other, vm| {
679                PyBytes::sequence_downcast(seq)
680                    .payload
681                    .inner
682                    .concat(other, vm)
683                    .map(|x| vm.ctx.new_bytes(x).into())
684            }),
685            repeat: atomic_func!(|seq, n, vm| {
686                let zelf = seq.obj.to_owned().downcast::<PyBytes>().map_err(|_| {
687                    vm.new_type_error("bad argument type for built-in operation".to_owned())
688                })?;
689                zelf.repeat(n, vm).to_pyresult(vm)
690            }),
691            item: atomic_func!(|seq, i, vm| {
692                PyBytes::sequence_downcast(seq)
693                    .as_bytes()
694                    .getitem_by_index(vm, i)
695                    .map(|x| vm.ctx.new_bytes(vec![x]).into())
696            }),
697            contains: atomic_func!(|seq, other, vm| {
698                PyBytes::sequence_downcast(seq).__contains__(other.to_owned(), vm)
699            }),
700            ..PySequenceMethods::NOT_IMPLEMENTED
701        });
702        &AS_SEQUENCE
703    }
704}
705
706impl AsNumber for PyBytes {
707    fn as_number() -> &'static PyNumberMethods {
708        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
709            remainder: Some(|a, b, vm| {
710                if let Some(a) = a.downcast_ref::<PyBytes>() {
711                    a.__mod__(b.to_owned(), vm).to_pyresult(vm)
712                } else {
713                    Ok(vm.ctx.not_implemented())
714                }
715            }),
716            ..PyNumberMethods::NOT_IMPLEMENTED
717        };
718        &AS_NUMBER
719    }
720}
721
722impl Hashable for PyBytes {
723    #[inline]
724    fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
725        Ok(zelf.payload.inner.hash(vm))
726    }
727}
728
729impl Comparable for PyBytes {
730    fn cmp(
731        zelf: &Py<Self>,
732        other: &PyObject,
733        op: PyComparisonOp,
734        vm: &VirtualMachine,
735    ) -> PyResult<PyComparisonValue> {
736        Ok(if let Some(res) = op.identical_optimization(zelf, other) {
737            res.into()
738        } else if other.fast_isinstance(vm.ctx.types.memoryview_type)
739            && op != PyComparisonOp::Eq
740            && op != PyComparisonOp::Ne
741        {
742            return Err(vm.new_type_error(format!(
743                "'{}' not supported between instances of '{}' and '{}'",
744                op.operator_token(),
745                zelf.class().slot_name(),
746                other.class().slot_name()
747            )));
748        } else {
749            zelf.payload.inner.cmp(other, op, vm)
750        })
751    }
752}
753
754impl Iterable for PyBytes {
755    fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
756        Ok(PyBytesIterator {
757            internal: PyMutex::new(PositionIterInternal::new(zelf, 0)),
758        }
759        .into_pyobject(vm))
760    }
761}
762
763impl Representable for PyBytes {
764    #[inline]
765    fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
766        zelf.payload.inner.repr_bytes(vm)
767    }
768}
769
770#[pyclass(module = false, name = "bytes_iterator")]
771#[derive(Debug)]
772pub(crate) struct PyBytesIterator {
773    internal: PyMutex<PositionIterInternal<PyBytesRef>>,
774}
775
776impl PyPayload for PyBytesIterator {
777    #[inline]
778    fn class(ctx: &Context) -> &'static Py<PyType> {
779        ctx.types.bytes_iterator_type
780    }
781}
782
783#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))]
784impl Py<PyBytesIterator> {
785    #[pymethod]
786    fn __length_hint__(&self) -> usize {
787        self.internal.lock().length_hint(|obj| obj.as_bytes().len())
788    }
789
790    #[pymethod]
791    fn __reduce__(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
792        let func = builtins_iter(vm)?;
793        Ok(self.internal.lock().reduce(
794            func,
795            |x| x.clone().into(),
796            |vm| vm.ctx.empty_tuple.clone().into(),
797            vm,
798        ))
799    }
800
801    #[pymethod]
802    fn __setstate__(&self, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
803        self.internal
804            .lock()
805            .set_state(&object, |obj, pos| pos.min(obj.as_bytes().len()), vm)
806    }
807}
808
809impl SelfIter for PyBytesIterator {}
810impl IterNext for PyBytesIterator {
811    fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
812        locked_next(&zelf.internal, |bytes, pos| {
813            Ok(PyIterReturn::from_result(
814                bytes
815                    .as_bytes()
816                    .get(pos)
817                    .map(|&x| vm.new_pyobj(x))
818                    .ok_or(None),
819            ))
820        })
821    }
822}
823
824impl<'a> TryFromBorrowedObject<'a> for PyBytes {
825    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
826        PyBytesInner::try_from_borrowed_object(vm, obj).map(|x| x.into())
827    }
828}