Skip to main content

rustpython_vm/builtins/
bool.rs

1use super::{PyInt, PyStrRef, PyType, PyTypeRef, PyUtf8StrRef};
2use crate::common::format::FormatSpec;
3use crate::{
4    AsObject, Context, Py, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, VirtualMachine,
5    class::{PyClassDef, PyClassImpl},
6    convert::{IntoPyException, ToPyObject, ToPyResult},
7    function::{FuncArgs, OptionalArg},
8    protocol::PyNumberMethods,
9    types::{AsNumber, Constructor, Representable},
10};
11use core::fmt::{Debug, Formatter};
12use num_traits::Zero;
13
14impl ToPyObject for bool {
15    fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
16        vm.ctx.new_bool(self).into()
17    }
18}
19
20impl<'a> TryFromBorrowedObject<'a> for bool {
21    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
22        // Python takes integers as a legit bool value
23        match obj.downcast_ref::<PyInt>() {
24            Some(int_obj) => {
25                let int_val = int_obj.as_bigint();
26                Ok(!int_val.is_zero())
27            }
28            None => {
29                Err(vm.new_type_error(format!("Expected type bool, not {}", obj.class().name())))
30            }
31        }
32    }
33}
34
35impl PyObject {
36    /// Convert Python bool into Rust bool.
37    ///
38    /// Takes `&self` rather than an owned reference so that the eval loop can
39    /// call it straight through a borrowed stack entry, without the reference
40    /// count round trip that owning the operand would cost.
41    #[inline(always)]
42    pub fn try_to_bool(&self, vm: &VirtualMachine) -> PyResult<bool> {
43        if self.is(&vm.ctx.true_value) {
44            return Ok(true);
45        } else if self.is(&vm.ctx.false_value) {
46            return Ok(false);
47        }
48
49        self.try_to_bool_slow(vm)
50    }
51
52    #[cold]
53    #[inline(never)]
54    fn try_to_bool_slow(&self, vm: &VirtualMachine) -> PyResult<bool> {
55        let slots = self.class().slots();
56
57        // 1. Try nb_bool slot first
58        if let Some(nb_bool) = slots.as_number.boolean.load() {
59            return nb_bool(self.as_object().number(), vm);
60        }
61
62        // 2. Try mp_length slot (mapping protocol)
63        if let Some(mp_length) = slots.as_mapping.length.load() {
64            let len = mp_length(self.as_object().mapping_unchecked(), vm)?;
65            return Ok(len != 0);
66        }
67
68        // 3. Try sq_length slot (sequence protocol)
69        if let Some(sq_length) = slots.as_sequence.length.load() {
70            let len = sq_length(self.as_object().sequence_unchecked(), vm)?;
71            return Ok(len != 0);
72        }
73
74        // 4. Default: objects without __bool__ or __len__ are truthy
75        Ok(true)
76    }
77}
78
79#[pyclass(name = "bool", module = false, base = PyInt, ctx = "bool_type")]
80#[repr(transparent)]
81pub struct PyBool(pub PyInt);
82
83impl Debug for PyBool {
84    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
85        let value = !self.0.as_bigint().is_zero();
86        write!(f, "PyBool({value})")
87    }
88}
89
90#[derive(FromArgs)]
91pub struct BoolArgs {
92    // Missing skips conversion and is False.
93    #[pyarg(positional, default, py_default = "False")]
94    object: OptionalArg<PyObjectRef>,
95}
96
97impl Constructor for PyBool {
98    type Args = BoolArgs;
99
100    fn slot_new(zelf: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
101        let x: Self::Args = args.bind_for(vm, Self::NAME)?;
102        let x = x.object;
103        if !zelf.fast_isinstance(vm.ctx.types.type_type) {
104            return Err(vm.new_type_error(format!(
105                "requires a 'type' object but received a '{}'",
106                zelf.class().name()
107            )));
108        }
109        let val = x.map_or(Ok(false), |val| val.try_to_bool(vm))?;
110        Ok(vm.ctx.new_bool(val).into())
111    }
112
113    fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
114        unimplemented!("use slot_new")
115    }
116}
117
118#[pyclass(with(Constructor, AsNumber, Representable), flags(_MATCH_SELF))]
119impl Py<PyBool> {
120    #[pymethod]
121    fn __format__(
122        zelf: PyObjectRef,
123        format_spec: PyUtf8StrRef,
124        vm: &VirtualMachine,
125    ) -> PyResult<String> {
126        let new_bool = zelf.try_to_bool(vm)?;
127        FormatSpec::parse(format_spec.as_str())
128            .and_then(|format_spec| format_spec.format_bool(new_bool))
129            .map_err(|err| err.into_pyexception(vm))
130    }
131}
132
133impl PyBool {
134    pub(crate) fn __or__(lhs: &PyObject, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
135        if lhs.fast_isinstance(vm.ctx.types.bool_type)
136            && rhs.fast_isinstance(vm.ctx.types.bool_type)
137        {
138            let lhs = get_value(lhs);
139            let rhs = get_value(&rhs);
140            (lhs || rhs).to_pyobject(vm)
141        } else if let Some(lhs) = lhs.downcast_ref::<PyInt>() {
142            lhs.__or__(rhs).to_pyobject(vm)
143        } else {
144            vm.ctx.not_implemented()
145        }
146    }
147
148    pub(crate) fn __and__(lhs: &PyObject, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
149        if lhs.fast_isinstance(vm.ctx.types.bool_type)
150            && rhs.fast_isinstance(vm.ctx.types.bool_type)
151        {
152            let lhs = get_value(lhs);
153            let rhs = get_value(&rhs);
154            (lhs && rhs).to_pyobject(vm)
155        } else if let Some(lhs) = lhs.downcast_ref::<PyInt>() {
156            lhs.__and__(rhs).to_pyobject(vm)
157        } else {
158            vm.ctx.not_implemented()
159        }
160    }
161
162    pub(crate) fn __xor__(lhs: &PyObject, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
163        if lhs.fast_isinstance(vm.ctx.types.bool_type)
164            && rhs.fast_isinstance(vm.ctx.types.bool_type)
165        {
166            let lhs = get_value(lhs);
167            let rhs = get_value(&rhs);
168            (lhs ^ rhs).to_pyobject(vm)
169        } else if let Some(lhs) = lhs.downcast_ref::<PyInt>() {
170            lhs.__xor__(rhs).to_pyobject(vm)
171        } else {
172            vm.ctx.not_implemented()
173        }
174    }
175}
176
177impl AsNumber for PyBool {
178    fn as_number() -> &'static PyNumberMethods {
179        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
180            and: Some(|a, b, vm| PyBool::__and__(a, b.to_owned(), vm).to_pyresult(vm)),
181            xor: Some(|a, b, vm| PyBool::__xor__(a, b.to_owned(), vm).to_pyresult(vm)),
182            or: Some(|a, b, vm| PyBool::__or__(a, b.to_owned(), vm).to_pyresult(vm)),
183            ..PyNumberMethods::NOT_IMPLEMENTED
184        };
185        &AS_NUMBER
186    }
187}
188
189impl Representable for PyBool {
190    #[inline]
191    fn slot_repr(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
192        let name = if get_value(zelf.as_object()) {
193            vm.ctx.names.True
194        } else {
195            vm.ctx.names.False
196        };
197        Ok(name.to_owned())
198    }
199
200    #[cold]
201    fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
202        unreachable!("use slot_repr instead")
203    }
204}
205
206fn vectorcall_bool(
207    zelf_obj: &PyObject,
208    args: Vec<PyObjectRef>,
209    nargs: usize,
210    kwnames: Option<&[PyObjectRef]>,
211    vm: &VirtualMachine,
212) -> PyResult {
213    let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
214    let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
215    (zelf.slots.new.load().unwrap())(zelf.to_owned(), func_args, vm)
216}
217
218pub(crate) fn init(context: &'static Context) {
219    PyBool::extend_class(context, context.types.bool_type);
220    context
221        .types
222        .bool_type
223        .slots
224        .vectorcall
225        .store(Some(vectorcall_bool));
226}
227
228// pub fn not(vm: &VirtualMachine, obj: &PyObject) -> PyResult<bool> {
229//     if obj.fast_isinstance(vm.ctx.types.bool_type) {
230//         let value = get_value(obj);
231//         Ok(!value)
232//     } else {
233//         Err(vm.new_type_error(format!("Can only invert a bool, on {:?}", obj)))
234//     }
235// }
236
237// Retrieve inner int value:
238pub(crate) fn get_value(obj: &PyObject) -> bool {
239    !obj.downcast_ref::<PyBool>()
240        .unwrap()
241        .0
242        .as_bigint()
243        .is_zero()
244}