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::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 PyObjectRef {
36    /// Convert Python bool into Rust bool.
37    pub fn try_to_bool(self, vm: &VirtualMachine) -> PyResult<bool> {
38        if self.is(&vm.ctx.true_value) {
39            return Ok(true);
40        }
41        if self.is(&vm.ctx.false_value) {
42            return Ok(false);
43        }
44
45        let slots = &self.class().slots;
46
47        // 1. Try nb_bool slot first
48        if let Some(nb_bool) = slots.as_number.boolean.load() {
49            return nb_bool(self.as_object().number(), vm);
50        }
51
52        // 2. Try mp_length slot (mapping protocol)
53        if let Some(mp_length) = slots.as_mapping.length.load() {
54            let len = mp_length(self.as_object().mapping_unchecked(), vm)?;
55            return Ok(len != 0);
56        }
57
58        // 3. Try sq_length slot (sequence protocol)
59        if let Some(sq_length) = slots.as_sequence.length.load() {
60            let len = sq_length(self.as_object().sequence_unchecked(), vm)?;
61            return Ok(len != 0);
62        }
63
64        // 4. Default: objects without __bool__ or __len__ are truthy
65        Ok(true)
66    }
67}
68
69#[pyclass(name = "bool", module = false, base = PyInt, ctx = "bool_type")]
70#[repr(transparent)]
71pub struct PyBool(pub PyInt);
72
73impl Debug for PyBool {
74    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
75        let value = !self.0.as_bigint().is_zero();
76        write!(f, "PyBool({})", value)
77    }
78}
79
80impl Constructor for PyBool {
81    type Args = OptionalArg<PyObjectRef>;
82
83    fn slot_new(zelf: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
84        let x: Self::Args = args.bind(vm)?;
85        if !zelf.fast_isinstance(vm.ctx.types.type_type) {
86            let actual_class = zelf.class();
87            let actual_type = &actual_class.name();
88            return Err(vm.new_type_error(format!(
89                "requires a 'type' object but received a '{actual_type}'"
90            )));
91        }
92        let val = x.map_or(Ok(false), |val| val.try_to_bool(vm))?;
93        Ok(vm.ctx.new_bool(val).into())
94    }
95
96    fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
97        unimplemented!("use slot_new")
98    }
99}
100
101#[pyclass(with(Constructor, AsNumber, Representable), flags(_MATCH_SELF))]
102impl PyBool {
103    #[pymethod]
104    fn __format__(obj: PyObjectRef, spec: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<String> {
105        let new_bool = obj.try_to_bool(vm)?;
106        FormatSpec::parse(spec.as_str())
107            .and_then(|format_spec| format_spec.format_bool(new_bool))
108            .map_err(|err| err.into_pyexception(vm))
109    }
110}
111
112impl PyBool {
113    pub(crate) fn __or__(lhs: PyObjectRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
114        if lhs.fast_isinstance(vm.ctx.types.bool_type)
115            && rhs.fast_isinstance(vm.ctx.types.bool_type)
116        {
117            let lhs = get_value(&lhs);
118            let rhs = get_value(&rhs);
119            (lhs || rhs).to_pyobject(vm)
120        } else if let Some(lhs) = lhs.downcast_ref::<PyInt>() {
121            lhs.__or__(rhs).to_pyobject(vm)
122        } else {
123            vm.ctx.not_implemented()
124        }
125    }
126
127    pub(crate) fn __and__(lhs: PyObjectRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
128        if lhs.fast_isinstance(vm.ctx.types.bool_type)
129            && rhs.fast_isinstance(vm.ctx.types.bool_type)
130        {
131            let lhs = get_value(&lhs);
132            let rhs = get_value(&rhs);
133            (lhs && rhs).to_pyobject(vm)
134        } else if let Some(lhs) = lhs.downcast_ref::<PyInt>() {
135            lhs.__and__(rhs).to_pyobject(vm)
136        } else {
137            vm.ctx.not_implemented()
138        }
139    }
140
141    pub(crate) fn __xor__(lhs: PyObjectRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
142        if lhs.fast_isinstance(vm.ctx.types.bool_type)
143            && rhs.fast_isinstance(vm.ctx.types.bool_type)
144        {
145            let lhs = get_value(&lhs);
146            let rhs = get_value(&rhs);
147            (lhs ^ rhs).to_pyobject(vm)
148        } else if let Some(lhs) = lhs.downcast_ref::<PyInt>() {
149            lhs.__xor__(rhs).to_pyobject(vm)
150        } else {
151            vm.ctx.not_implemented()
152        }
153    }
154}
155
156impl AsNumber for PyBool {
157    fn as_number() -> &'static PyNumberMethods {
158        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
159            and: Some(|a, b, vm| PyBool::__and__(a.to_owned(), b.to_owned(), vm).to_pyresult(vm)),
160            xor: Some(|a, b, vm| PyBool::__xor__(a.to_owned(), b.to_owned(), vm).to_pyresult(vm)),
161            or: Some(|a, b, vm| PyBool::__or__(a.to_owned(), b.to_owned(), vm).to_pyresult(vm)),
162            ..PyInt::AS_NUMBER
163        };
164        &AS_NUMBER
165    }
166}
167
168impl Representable for PyBool {
169    #[inline]
170    fn slot_repr(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
171        let name = if get_value(zelf.as_object()) {
172            vm.ctx.names.True
173        } else {
174            vm.ctx.names.False
175        };
176        Ok(name.to_owned())
177    }
178
179    #[cold]
180    fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
181        unreachable!("use slot_repr instead")
182    }
183}
184
185fn vectorcall_bool(
186    zelf_obj: &PyObject,
187    args: Vec<PyObjectRef>,
188    nargs: usize,
189    kwnames: Option<&[PyObjectRef]>,
190    vm: &VirtualMachine,
191) -> PyResult {
192    let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
193    let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
194    (zelf.slots.new.load().unwrap())(zelf.to_owned(), func_args, vm)
195}
196
197pub(crate) fn init(context: &'static Context) {
198    PyBool::extend_class(context, context.types.bool_type);
199    context
200        .types
201        .bool_type
202        .slots
203        .vectorcall
204        .store(Some(vectorcall_bool));
205}
206
207// pub fn not(vm: &VirtualMachine, obj: &PyObject) -> PyResult<bool> {
208//     if obj.fast_isinstance(vm.ctx.types.bool_type) {
209//         let value = get_value(obj);
210//         Ok(!value)
211//     } else {
212//         Err(vm.new_type_error(format!("Can only invert a bool, on {:?}", obj)))
213//     }
214// }
215
216// Retrieve inner int value:
217pub(crate) fn get_value(obj: &PyObject) -> bool {
218    !obj.downcast_ref::<PyBool>()
219        .unwrap()
220        .0
221        .as_bigint()
222        .is_zero()
223}