Skip to main content

rustpython_vm/vm/
vm_ops.rs

1use super::VirtualMachine;
2use crate::stdlib::_warnings;
3use crate::{
4    Py, PyRef,
5    builtins::{PyInt, PyStr, PyStrInterned, PyStrRef, PyType, PyUtf8Str},
6    object::{AsObject, PyObject, PyObjectRef, PyResult},
7    protocol::{PyNumberBinaryOp, PyNumberSlots, PyNumberTernaryOp, PyNumberUnaryFunc, PySequence},
8    types::PyComparisonOp,
9};
10use num_traits::ToPrimitive;
11
12/// [CPython `method_is_overloaded`](https://github.com/python/cpython/blob/v3.14.3/Objects/typeobject.c#L9849-L9879)
13fn method_is_overloaded(
14    class_a: &Py<PyType>,
15    class_b: &Py<PyType>,
16    rop_name: Option<&'static PyStrInterned>,
17    vm: &VirtualMachine,
18) -> PyResult<bool> {
19    let Some(rop_name) = rop_name else {
20        return Ok(false);
21    };
22    let Some(method_b) = class_b.get_attr(rop_name) else {
23        return Ok(false);
24    };
25    class_a.get_attr(rop_name).map_or(Ok(true), |method_a| {
26        vm.identical_or_equal(&method_a, &method_b).map(|eq| !eq)
27    })
28}
29
30macro_rules! binary_func {
31    ($fn:ident, $op_slot:ident, $op:expr) => {
32        pub fn $fn(&self, a: &PyObject, b: &PyObject) -> PyResult {
33            self.binary_op(a, b, PyNumberBinaryOp::$op_slot, $op)
34        }
35    };
36}
37
38macro_rules! ternary_func {
39    ($fn:ident, $op_slot:ident, $op:expr) => {
40        pub fn $fn(&self, a: &PyObject, b: &PyObject, c: &PyObject) -> PyResult {
41            self.ternary_op(a, b, c, PyNumberTernaryOp::$op_slot, $op)
42        }
43    };
44}
45
46macro_rules! inplace_binary_func {
47    ($fn:ident, $iop_slot:ident, $op_slot:ident, $op:expr) => {
48        pub fn $fn(&self, a: &PyObject, b: &PyObject) -> PyResult {
49            self.binary_iop(
50                a,
51                b,
52                PyNumberBinaryOp::$iop_slot,
53                PyNumberBinaryOp::$op_slot,
54                $op,
55            )
56        }
57    };
58}
59
60macro_rules! inplace_ternary_func {
61    ($fn:ident, $iop_slot:ident, $op_slot:ident, $op:expr) => {
62        pub fn $fn(&self, a: &PyObject, b: &PyObject, c: &PyObject) -> PyResult {
63            self.ternary_iop(
64                a,
65                b,
66                c,
67                PyNumberTernaryOp::$iop_slot,
68                PyNumberTernaryOp::$op_slot,
69                $op,
70            )
71        }
72    };
73}
74
75/// Collection of operators
76impl VirtualMachine {
77    #[inline]
78    pub fn bool_eq(&self, a: &PyObject, b: &PyObject) -> PyResult<bool> {
79        a.rich_compare_bool(b, PyComparisonOp::Eq, self)
80    }
81
82    pub fn identical_or_equal(&self, a: &PyObject, b: &PyObject) -> PyResult<bool> {
83        if a.is(b) {
84            Ok(true)
85        } else {
86            self.bool_eq(a, b)
87        }
88    }
89
90    pub fn bool_seq_lt(&self, a: &PyObject, b: &PyObject) -> PyResult<Option<bool>> {
91        let value = if a.rich_compare_bool(b, PyComparisonOp::Lt, self)? {
92            Some(true)
93        } else if !self.bool_eq(a, b)? {
94            Some(false)
95        } else {
96            None
97        };
98        Ok(value)
99    }
100
101    pub fn bool_seq_gt(&self, a: &PyObject, b: &PyObject) -> PyResult<Option<bool>> {
102        let value = if a.rich_compare_bool(b, PyComparisonOp::Gt, self)? {
103            Some(true)
104        } else if !self.bool_eq(a, b)? {
105            Some(false)
106        } else {
107            None
108        };
109        Ok(value)
110    }
111
112    pub fn length_hint_opt(&self, iter: PyObjectRef) -> PyResult<Option<usize>> {
113        // Ask for a length only from something that could have one. `length()`
114        // answers a type with no length slot -- every iterator, every
115        // generator, which is most of what gets passed here -- by building a
116        // `TypeError` this caller immediately throws away. CPython's
117        // `PyObject_LengthHint` gates the call on the slots for the same
118        // reason.
119        if let Some(len) = iter.length_opt(self) {
120            match len {
121                Ok(len) => return Ok(Some(len)),
122                Err(e) => {
123                    if !e.fast_isinstance(self.ctx.exceptions.type_error) {
124                        return Err(e);
125                    }
126                }
127            }
128        }
129        let hint = match self.get_method(iter, identifier!(self, __length_hint__)) {
130            Some(hint) => hint?,
131            None => return Ok(None),
132        };
133        let result = match hint.call((), self) {
134            Ok(res) => {
135                if res.is(&self.ctx.not_implemented) {
136                    return Ok(None);
137                }
138                res
139            }
140            Err(e) => {
141                return if e.fast_isinstance(self.ctx.exceptions.type_error) {
142                    Ok(None)
143                } else {
144                    Err(e)
145                };
146            }
147        };
148        let hint = result
149            .downcast_ref::<PyInt>()
150            .ok_or_else(|| {
151                self.new_type_error(format!(
152                    "'{}' object cannot be interpreted as an integer",
153                    result.class().name()
154                ))
155            })?
156            .try_to_primitive::<isize>(self)?;
157        if hint.is_negative() {
158            Err(self.new_value_error("__length_hint__() should return >= 0"))
159        } else {
160            Ok(Some(hint as usize))
161        }
162    }
163
164    /// Checks that the multiplication is able to be performed. On Ok returns the
165    /// index as a usize for sequences to be able to use immediately.
166    pub fn check_repeat_or_overflow_error(&self, length: usize, n: isize) -> PyResult<usize> {
167        if n <= 0 {
168            Ok(0)
169        } else {
170            let n = n as usize;
171            if length > crate::stdlib::sys::MAXSIZE as usize / n {
172                Err(self.new_overflow_error("repeated value are too long"))
173            } else {
174                Ok(n)
175            }
176        }
177    }
178
179    /// `vec![0; len]` for a length that came from Python, where a request too
180    /// large to satisfy is a `MemoryError` rather than an aborted process.
181    ///
182    /// The bytes are left for the allocator to zero, so a large request costs
183    /// no more than the pages that are actually written to.
184    pub fn new_zeroed_bytes(&self, len: usize) -> PyResult<Vec<u8>> {
185        if len == 0 {
186            return Ok(Vec::new());
187        }
188        let layout = core::alloc::Layout::array::<u8>(len).map_err(|_| self.no_memory_error())?;
189        // SAFETY: `len` is not zero, so neither is the layout's size.
190        let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
191        if ptr.is_null() {
192            return Err(self.no_memory_error());
193        }
194        // SAFETY: `ptr` was just allocated by the global allocator for exactly
195        // this many bytes, and every one of them is initialized to zero.
196        Ok(unsafe { Vec::from_raw_parts(ptr, len, len) })
197    }
198
199    /// Calling scheme used for binary operations:
200    ///
201    /// Order operations are tried until either a valid result or error:
202    ///   `b.rop(b,a)[*], a.op(a,b), b.rop(b,a)`
203    ///
204    /// `[*]` - only when Py_TYPE(a) != Py_TYPE(b) && Py_TYPE(b) is a subclass of Py_TYPE(a)
205    pub fn binary_op1(&self, a: &PyObject, b: &PyObject, op_slot: PyNumberBinaryOp) -> PyResult {
206        let class_a = a.class();
207        let class_b = b.class();
208
209        // Number slots are inherited, direct access is O(1)
210        let slot_a = class_a.slots.as_number.left_binary_op(op_slot);
211        let slot_a_addr = slot_a.map(|x| crate::types::fn_addr(x));
212        let mut slot_b = None;
213        let left_b_addr = if class_a.is(class_b) {
214            slot_a_addr
215        } else {
216            let slot_bb = class_b.slots.as_number.right_binary_op(op_slot);
217            if slot_bb.map(|x| crate::types::fn_addr(x)) != slot_a_addr {
218                slot_b = slot_bb;
219            }
220
221            class_b
222                .slots
223                .as_number
224                .left_binary_op(op_slot)
225                .map(|x| crate::types::fn_addr(x))
226        };
227
228        if let Some(slot_a) = slot_a {
229            if let Some(slot_bb) = slot_b
230                && class_b.fast_issubclass(class_a)
231                && (slot_a_addr != left_b_addr
232                    || method_is_overloaded(
233                        class_a,
234                        class_b,
235                        op_slot.right_method_name(self),
236                        self,
237                    )?)
238            {
239                let ret = slot_bb(a, b, self)?;
240                if !ret.is(&self.ctx.not_implemented) {
241                    return Ok(ret);
242                }
243                slot_b = None;
244            }
245            let ret = slot_a(a, b, self)?;
246            if !ret.is(&self.ctx.not_implemented) {
247                return Ok(ret);
248            }
249        }
250
251        if let Some(slot_b) = slot_b {
252            let ret = slot_b(a, b, self)?;
253            if !ret.is(&self.ctx.not_implemented) {
254                return Ok(ret);
255            }
256        }
257
258        Ok(self.ctx.not_implemented())
259    }
260
261    pub fn binary_op(
262        &self,
263        a: &PyObject,
264        b: &PyObject,
265        op_slot: PyNumberBinaryOp,
266        op: &str,
267    ) -> PyResult {
268        let result = self.binary_op1(a, b, op_slot)?;
269        if !result.is(&self.ctx.not_implemented) {
270            return Ok(result);
271        }
272        Err(self.new_unsupported_bin_op_error(a, b, op))
273    }
274
275    /// Binary in-place operators
276    ///
277    /// The in-place operators are defined to fall back to the 'normal',
278    /// non in-place operations, if the in-place methods are not in place.
279    ///
280    /// - If the left hand object has the appropriate struct members, and
281    ///   they are filled, call the appropriate function and return the
282    ///   result.  No coercion is done on the arguments; the left-hand object
283    ///   is the one the operation is performed on, and it's up to the
284    ///   function to deal with the right-hand object.
285    ///
286    /// - Otherwise, in-place modification is not supported. Handle it exactly as
287    ///   a non in-place operation of the same kind.
288    pub(crate) fn binary_iop1(
289        &self,
290        a: &PyObject,
291        b: &PyObject,
292        iop_slot: PyNumberBinaryOp,
293        op_slot: PyNumberBinaryOp,
294    ) -> PyResult {
295        if let Some(slot) = a.class().slots().as_number.left_binary_op(iop_slot) {
296            let x = slot(a, b, self)?;
297            if !x.is(&self.ctx.not_implemented) {
298                return Ok(x);
299            }
300        }
301        self.binary_op1(a, b, op_slot)
302    }
303
304    fn binary_iop(
305        &self,
306        a: &PyObject,
307        b: &PyObject,
308        iop_slot: PyNumberBinaryOp,
309        op_slot: PyNumberBinaryOp,
310        op: &str,
311    ) -> PyResult {
312        let result = self.binary_iop1(a, b, iop_slot, op_slot)?;
313        if !result.is(&self.ctx.not_implemented) {
314            return Ok(result);
315        }
316        Err(self.new_unsupported_bin_op_error(a, b, op))
317    }
318
319    fn ternary_op(
320        &self,
321        a: &PyObject,
322        b: &PyObject,
323        c: &PyObject,
324        op_slot: PyNumberTernaryOp,
325        op_str: &str,
326    ) -> PyResult {
327        let class_a = a.class();
328        let class_b = b.class();
329        let class_c = c.class();
330
331        // Number slots are inherited, direct access is O(1)
332        let slot_a = class_a.slots.as_number.left_ternary_op(op_slot);
333        let slot_a_addr = slot_a.map(|x| crate::types::fn_addr(x));
334        let mut slot_b = None;
335        let left_b_addr = if class_a.is(class_b) {
336            slot_a_addr
337        } else {
338            let slot_bb = class_b.slots.as_number.right_ternary_op(op_slot);
339            if slot_bb.map(|x| crate::types::fn_addr(x)) != slot_a_addr {
340                slot_b = slot_bb;
341            }
342
343            class_b
344                .slots
345                .as_number
346                .left_ternary_op(op_slot)
347                .map(|x| crate::types::fn_addr(x))
348        };
349
350        if let Some(slot_a) = slot_a {
351            if let Some(slot_bb) = slot_b
352                && class_b.fast_issubclass(class_a)
353                && (slot_a_addr != left_b_addr
354                    || method_is_overloaded(
355                        class_a,
356                        class_b,
357                        op_slot.right_method_name(self),
358                        self,
359                    )?)
360            {
361                let ret = slot_bb(a, b, c, self)?;
362                if !ret.is(&self.ctx.not_implemented) {
363                    return Ok(ret);
364                }
365                slot_b = None;
366            }
367            let ret = slot_a(a, b, c, self)?;
368            if !ret.is(&self.ctx.not_implemented) {
369                return Ok(ret);
370            }
371        }
372
373        if let Some(slot_b) = slot_b {
374            let ret = slot_b(a, b, c, self)?;
375            if !ret.is(&self.ctx.not_implemented) {
376                return Ok(ret);
377            }
378        }
379
380        // The modulus gets its turn whenever its slot is not one of the two
381        // already tried, which includes the case where neither operand had one:
382        // `pow(10, 2, Decimal(7))` reaches `Decimal` only this way.
383        if let Some(slot_c) = class_c.slots.as_number.left_ternary_op(op_slot)
384            && slot_a.is_none_or(|slot_a| !core::ptr::fn_addr_eq(slot_a, slot_c))
385            && slot_b.is_none_or(|slot_b| !core::ptr::fn_addr_eq(slot_b, slot_c))
386        {
387            let ret = slot_c(a, b, c, self)?;
388            if !ret.is(&self.ctx.not_implemented) {
389                return Ok(ret);
390            }
391        }
392
393        Err(if self.is_none(c) {
394            self.new_type_error(format!(
395                "unsupported operand type(s) for {}: \
396                '{}' and '{}'",
397                op_str,
398                a.class().slot_name(),
399                b.class().slot_name()
400            ))
401        } else {
402            self.new_type_error(format!(
403                "unsupported operand type(s) for {}: \
404                '{}', '{}', '{}'",
405                op_str,
406                a.class().slot_name(),
407                b.class().slot_name(),
408                c.class().slot_name()
409            ))
410        })
411    }
412
413    fn ternary_iop(
414        &self,
415        a: &PyObject,
416        b: &PyObject,
417        c: &PyObject,
418        iop_slot: PyNumberTernaryOp,
419        op_slot: PyNumberTernaryOp,
420        op_str: &str,
421    ) -> PyResult {
422        if let Some(slot) = a.class().slots().as_number.left_ternary_op(iop_slot) {
423            let x = slot(a, b, c, self)?;
424            if !x.is(&self.ctx.not_implemented) {
425                return Ok(x);
426            }
427        }
428        self.ternary_op(a, b, c, op_slot, op_str)
429    }
430
431    binary_func!(_sub, Subtract, "-");
432    binary_func!(_mod, Remainder, "%");
433    binary_func!(_divmod, Divmod, "divmod()");
434    binary_func!(_lshift, Lshift, "<<");
435    binary_func!(_rshift, Rshift, ">>");
436    binary_func!(_and, And, "&");
437    binary_func!(_xor, Xor, "^");
438    binary_func!(_or, Or, "|");
439    binary_func!(_floordiv, FloorDivide, "//");
440    binary_func!(_truediv, TrueDivide, "/");
441    binary_func!(_matmul, MatrixMultiply, "@");
442
443    inplace_binary_func!(_isub, InplaceSubtract, Subtract, "-=");
444    inplace_binary_func!(_imod, InplaceRemainder, Remainder, "%=");
445    inplace_binary_func!(_ilshift, InplaceLshift, Lshift, "<<=");
446    inplace_binary_func!(_irshift, InplaceRshift, Rshift, ">>=");
447    inplace_binary_func!(_iand, InplaceAnd, And, "&=");
448    inplace_binary_func!(_ixor, InplaceXor, Xor, "^=");
449    inplace_binary_func!(_ior, InplaceOr, Or, "|=");
450    inplace_binary_func!(_ifloordiv, InplaceFloorDivide, FloorDivide, "//=");
451    inplace_binary_func!(_itruediv, InplaceTrueDivide, TrueDivide, "/=");
452    inplace_binary_func!(_imatmul, InplaceMatrixMultiply, MatrixMultiply, "@=");
453
454    ternary_func!(_pow, Power, "** or pow()");
455    inplace_ternary_func!(_ipow, InplacePower, Power, "**=");
456
457    pub fn _add(&self, a: &PyObject, b: &PyObject) -> PyResult {
458        let result = self.binary_op1(a, b, PyNumberBinaryOp::Add)?;
459        if !result.is(&self.ctx.not_implemented) {
460            return Ok(result);
461        }
462        // Check if concat slot is available directly, matching PyNumber_Add behavior
463        let seq = a.sequence_unchecked();
464        if let Some(f) = seq.slots().concat.load() {
465            let result = f(seq, b, self)?;
466            if !result.is(&self.ctx.not_implemented) {
467                return Ok(result);
468            }
469        }
470        Err(self.new_unsupported_bin_op_error(a, b, "+"))
471    }
472
473    pub fn _iadd(&self, a: &PyObject, b: &PyObject) -> PyResult {
474        let result = self.binary_iop1(a, b, PyNumberBinaryOp::InplaceAdd, PyNumberBinaryOp::Add)?;
475        if !result.is(&self.ctx.not_implemented) {
476            return Ok(result);
477        }
478        // Check inplace_concat or concat slot directly, matching PyNumber_InPlaceAdd behavior
479        let seq = a.sequence_unchecked();
480        let slots = seq.slots();
481        if let Some(f) = slots.inplace_concat.load().or_else(|| slots.concat.load()) {
482            let result = f(seq, b, self)?;
483            if !result.is(&self.ctx.not_implemented) {
484                return Ok(result);
485            }
486        }
487        Err(self.new_unsupported_bin_op_error(a, b, "+="))
488    }
489
490    pub fn _mul(&self, a: &PyObject, b: &PyObject) -> PyResult {
491        let result = self.binary_op1(a, b, PyNumberBinaryOp::Multiply)?;
492        if !result.is(&self.ctx.not_implemented) {
493            return Ok(result);
494        }
495        if let Some(f) = a.sequence_unchecked().slots().repeat.load() {
496            return self.sequence_repeat(f, a, b);
497        }
498        if let Some(f) = b.sequence_unchecked().slots().repeat.load() {
499            return self.sequence_repeat(f, b, a);
500        }
501        Err(self.new_unsupported_bin_op_error(a, b, "*"))
502    }
503
504    pub fn _imul(&self, a: &PyObject, b: &PyObject) -> PyResult {
505        let result = self.binary_iop1(
506            a,
507            b,
508            PyNumberBinaryOp::InplaceMultiply,
509            PyNumberBinaryOp::Multiply,
510        )?;
511        if !result.is(&self.ctx.not_implemented) {
512            return Ok(result);
513        }
514        let a_seq = a.sequence_unchecked();
515        let a_slots = a_seq.slots();
516        if let Some(f) = a_slots
517            .inplace_repeat
518            .load()
519            .or_else(|| a_slots.repeat.load())
520        {
521            return self.sequence_repeat(f, a, b);
522        }
523        // The right operand is only tried when the left type has no sequence table at all,
524        // and every heap type has one. It is repeated, never mutated in place.
525        if !a_slots.has_any()
526            && a.class().heaptype_ext().is_none()
527            && let Some(f) = b.sequence_unchecked().slots().repeat.load()
528        {
529            return self.sequence_repeat(f, b, a);
530        }
531        Err(self.new_unsupported_bin_op_error(a, b, "*="))
532    }
533
534    // sequence_repeat in CPython
535    fn sequence_repeat(
536        &self,
537        repeat: fn(PySequence<'_>, isize, &Self) -> PyResult,
538        seq: &PyObject,
539        n: &PyObject,
540    ) -> PyResult {
541        let index = n.try_index_opt(self).ok_or_else(|| {
542            self.new_type_error(format!(
543                "can't multiply sequence by non-int of type '{}'",
544                n.class().slot_name()
545            ))
546        })??;
547        let count = index.as_bigint().to_isize().ok_or_else(|| {
548            self.new_overflow_error(format!(
549                "cannot fit '{}' into an index-sized integer",
550                n.class().slot_name()
551            ))
552        })?;
553        repeat(seq.sequence_unchecked(), count, self)
554    }
555
556    fn unary_op(
557        &self,
558        a: &PyObject,
559        slot: impl FnOnce(&PyNumberSlots) -> Option<PyNumberUnaryFunc>,
560        op: &str,
561    ) -> PyResult {
562        let f = slot(&a.class().slots.as_number)
563            .ok_or_else(|| self.new_unsupported_unary_error(a, op))?;
564        f(a.number(), self)
565    }
566
567    // PyNumber_Absolute
568    pub fn _abs(&self, a: &PyObject) -> PyResult<PyObjectRef> {
569        self.unary_op(a, |s| s.absolute.load(), "abs()")
570    }
571
572    // PyNumber_Positive
573    pub fn _pos(&self, a: &PyObject) -> PyResult {
574        self.unary_op(a, |s| s.positive.load(), "unary +")
575    }
576
577    // PyNumber_Negative
578    pub fn _neg(&self, a: &PyObject) -> PyResult {
579        self.unary_op(a, |s| s.negative.load(), "unary -")
580    }
581
582    pub fn _invert(&self, a: &PyObject) -> PyResult {
583        const STR: &str = "Bitwise inversion '~' on bool is deprecated and will be removed in Python 3.16. \
584            This returns the bitwise inversion of the underlying int object and is usually not what you expect from negating a bool. \
585            Use the 'not' operator for boolean negation or ~int(x) if you really want the bitwise inversion of the underlying int.";
586        if a.fast_isinstance(self.ctx.types.bool_type) {
587            _warnings::warn(
588                self.ctx.exceptions.deprecation_warning,
589                STR.to_owned(),
590                1,
591                self,
592            )?;
593        }
594        self.unary_op(a, |s| s.invert.load(), "unary ~")
595    }
596
597    // PyObject_Format
598    pub fn format(&self, obj: &PyObject, format_spec: PyStrRef) -> PyResult<PyStrRef> {
599        if format_spec.is_empty() {
600            let obj = match obj.to_owned().downcast_exact::<PyStr>(self) {
601                Ok(s) => return Ok(s.into_pyref()),
602                Err(obj) => obj,
603            };
604            if obj.class().is(self.ctx.types.int_type) {
605                return obj.str(self);
606            }
607        }
608        let bound_format = self
609            .get_special_method(obj, identifier!(self, __format__))?
610            .ok_or_else(|| {
611                self.new_type_error(format!(
612                    "Type {} doesn't define __format__",
613                    obj.class().name()
614                ))
615            })?;
616        let formatted = bound_format.invoke((format_spec,), self)?;
617        formatted.downcast().map_err(|result| {
618            self.new_type_error(format!(
619                "__format__ must return a str, not {}",
620                result.class().name()
621            ))
622        })
623    }
624    pub fn format_utf8(&self, obj: &PyObject, format_spec: PyStrRef) -> PyResult<PyRef<PyUtf8Str>> {
625        self.format(obj, format_spec)?.try_into_utf8(self)
626    }
627
628    pub fn _contains(&self, haystack: &PyObject, needle: &PyObject) -> PyResult<bool> {
629        let seq = haystack.sequence_unchecked();
630        seq.contains(needle, self)
631    }
632}