Skip to main content

pyo3/types/
boolobject.rs

1use super::any::PyAnyMethods;
2use crate::PyErr;
3use crate::conversion::IntoPyObject;
4#[cfg(feature = "experimental-inspect")]
5use crate::inspect::PyStaticExpr;
6#[cfg(feature = "experimental-inspect")]
7use crate::type_object::PyTypeInfo;
8use crate::{
9    Borrowed, FromPyObject, PyAny, Python, exceptions::PyTypeError, ffi, ffi_ptr_ext::FfiPtrExt,
10    instance::Bound, types::typeobject::PyTypeMethods,
11};
12#[cfg(RustPython)]
13use crate::{Py, sync::PyOnceLock, types::PyType};
14use core::convert::Infallible;
15use core::ptr;
16
17/// Represents a Python `bool`.
18///
19/// Values of this type are accessed via PyO3's smart pointers, e.g. as
20/// [`Py<PyBool>`][crate::Py] or [`Bound<'py, PyBool>`][Bound].
21///
22/// For APIs available on `bool` objects, see the [`PyBoolMethods`] trait which is implemented for
23/// [`Bound<'py, PyBool>`][Bound].
24#[repr(transparent)]
25pub struct PyBool(PyAny);
26
27#[cfg(not(RustPython))]
28pyobject_native_type!(PyBool, ffi::PyObject, pyobject_native_static_type_object!(ffi::PyBool_Type), "builtins", "bool", #checkfunction=ffi::PyBool_Check);
29
30#[cfg(RustPython)]
31pyobject_native_type!(
32    PyBool,
33    ffi::PyObject,
34    |py| {
35        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
36        TYPE.import(py, "builtins", "bool").unwrap().as_type_ptr()
37    },
38    "builtins",
39    "bool",
40    #checkfunction=ffi::PyBool_Check
41);
42
43impl PyBool {
44    /// Depending on `val`, returns `true` or `false`.
45    ///
46    /// # Note
47    /// This returns a [`Borrowed`] reference to one of Pythons `True` or
48    /// `False` singletons
49    #[inline]
50    pub fn new(py: Python<'_>, val: bool) -> Borrowed<'_, '_, Self> {
51        // SAFETY: `Py_True` and `Py_False` are global singletons which are known to be boolean objects
52        unsafe {
53            if val { ffi::Py_True() } else { ffi::Py_False() }
54                .assume_borrowed_unchecked(py)
55                .cast_unchecked()
56        }
57    }
58}
59
60/// Implementation of functionality for [`PyBool`].
61///
62/// These methods are defined for the `Bound<'py, PyBool>` smart pointer, so to use method call
63/// syntax these methods are separated into a trait, because stable Rust does not yet support
64/// `arbitrary_self_types`.
65#[doc(alias = "PyBool")]
66pub trait PyBoolMethods<'py>: crate::sealed::Sealed {
67    /// Gets whether this boolean is `true`.
68    fn is_true(&self) -> bool;
69}
70
71impl<'py> PyBoolMethods<'py> for Bound<'py, PyBool> {
72    #[inline]
73    fn is_true(&self) -> bool {
74        unsafe { ptr::eq(self.as_ptr(), ffi::Py_True()) }
75    }
76}
77
78/// Compare `Bound<PyBool>` with `bool`.
79impl PartialEq<bool> for Bound<'_, PyBool> {
80    #[inline]
81    fn eq(&self, other: &bool) -> bool {
82        self.as_borrowed() == *other
83    }
84}
85
86/// Compare `&Bound<PyBool>` with `bool`.
87impl PartialEq<bool> for &'_ Bound<'_, PyBool> {
88    #[inline]
89    fn eq(&self, other: &bool) -> bool {
90        self.as_borrowed() == *other
91    }
92}
93
94/// Compare `Bound<PyBool>` with `&bool`.
95impl PartialEq<&'_ bool> for Bound<'_, PyBool> {
96    #[inline]
97    fn eq(&self, other: &&bool) -> bool {
98        self.as_borrowed() == **other
99    }
100}
101
102/// Compare `bool` with `Bound<PyBool>`
103impl PartialEq<Bound<'_, PyBool>> for bool {
104    #[inline]
105    fn eq(&self, other: &Bound<'_, PyBool>) -> bool {
106        *self == other.as_borrowed()
107    }
108}
109
110/// Compare `bool` with `&Bound<PyBool>`
111impl PartialEq<&'_ Bound<'_, PyBool>> for bool {
112    #[inline]
113    fn eq(&self, other: &&'_ Bound<'_, PyBool>) -> bool {
114        *self == other.as_borrowed()
115    }
116}
117
118/// Compare `&bool` with `Bound<PyBool>`
119impl PartialEq<Bound<'_, PyBool>> for &'_ bool {
120    #[inline]
121    fn eq(&self, other: &Bound<'_, PyBool>) -> bool {
122        **self == other.as_borrowed()
123    }
124}
125
126/// Compare `Borrowed<PyBool>` with `bool`
127impl PartialEq<bool> for Borrowed<'_, '_, PyBool> {
128    #[inline]
129    fn eq(&self, other: &bool) -> bool {
130        self.is_true() == *other
131    }
132}
133
134/// Compare `Borrowed<PyBool>` with `&bool`
135impl PartialEq<&bool> for Borrowed<'_, '_, PyBool> {
136    #[inline]
137    fn eq(&self, other: &&bool) -> bool {
138        self.is_true() == **other
139    }
140}
141
142/// Compare `bool` with `Borrowed<PyBool>`
143impl PartialEq<Borrowed<'_, '_, PyBool>> for bool {
144    #[inline]
145    fn eq(&self, other: &Borrowed<'_, '_, PyBool>) -> bool {
146        *self == other.is_true()
147    }
148}
149
150/// Compare `&bool` with `Borrowed<PyBool>`
151impl PartialEq<Borrowed<'_, '_, PyBool>> for &'_ bool {
152    #[inline]
153    fn eq(&self, other: &Borrowed<'_, '_, PyBool>) -> bool {
154        **self == other.is_true()
155    }
156}
157
158impl<'py> IntoPyObject<'py> for bool {
159    type Target = PyBool;
160    type Output = Borrowed<'py, 'py, Self::Target>;
161    type Error = Infallible;
162
163    #[cfg(feature = "experimental-inspect")]
164    const OUTPUT_TYPE: PyStaticExpr = PyBool::TYPE_HINT;
165
166    #[inline]
167    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
168        Ok(PyBool::new(py, self))
169    }
170}
171
172impl<'py> IntoPyObject<'py> for &bool {
173    type Target = PyBool;
174    type Output = Borrowed<'py, 'py, Self::Target>;
175    type Error = Infallible;
176
177    #[cfg(feature = "experimental-inspect")]
178    const OUTPUT_TYPE: PyStaticExpr = bool::OUTPUT_TYPE;
179
180    #[inline]
181    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
182        (*self).into_pyobject(py)
183    }
184}
185
186/// Converts a Python `bool` to a Rust `bool`.
187///
188/// Fails with `TypeError` if the input is not a Python `bool`.
189impl FromPyObject<'_, '_> for bool {
190    type Error = PyErr;
191
192    #[cfg(feature = "experimental-inspect")]
193    const INPUT_TYPE: PyStaticExpr = PyBool::TYPE_HINT;
194
195    fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
196        let err = match obj.cast::<PyBool>() {
197            Ok(obj) => return Ok(obj.is_true()),
198            Err(err) => err,
199        };
200
201        let is_numpy_bool = {
202            let ty = obj.get_type();
203            ty.module().is_ok_and(|module| module == "numpy")
204                && ty
205                    .name()
206                    .is_ok_and(|name| name == "bool_" || name == "bool")
207        };
208
209        if is_numpy_bool {
210            let missing_conversion = |obj: Borrowed<'_, '_, PyAny>| {
211                PyTypeError::new_err(format!(
212                    "object of type '{}' does not define a '__bool__' conversion",
213                    obj.get_type()
214                ))
215            };
216
217            #[cfg(not(any(Py_LIMITED_API, PyPy)))]
218            unsafe {
219                let ptr = obj.as_ptr();
220
221                if let Some(tp_as_number) = (*(*ptr).ob_type).tp_as_number.as_ref() {
222                    if let Some(nb_bool) = tp_as_number.nb_bool {
223                        match (nb_bool)(ptr) {
224                            0 => return Ok(false),
225                            1 => return Ok(true),
226                            _ => return Err(crate::PyErr::fetch(obj.py())),
227                        }
228                    }
229                }
230
231                return Err(missing_conversion(obj));
232            }
233
234            #[cfg(any(Py_LIMITED_API, PyPy))]
235            {
236                let meth = obj
237                    .lookup_special(crate::intern!(obj.py(), "__bool__"))?
238                    .ok_or_else(|| missing_conversion(obj))?;
239
240                let obj = meth.call0()?.cast_into::<PyBool>()?;
241                return Ok(obj.is_true());
242            }
243        }
244
245        Err(err.into())
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use crate::IntoPyObject;
252    use crate::Python;
253    use crate::types::{PyAnyMethods, PyBool, PyBoolMethods};
254
255    #[test]
256    fn test_true() {
257        Python::attach(|py| {
258            assert!(PyBool::new(py, true).is_true());
259            let t = PyBool::new(py, true);
260            assert!(t.extract::<bool>().unwrap());
261            assert!(true.into_pyobject(py).unwrap().is(&*PyBool::new(py, true)));
262        });
263    }
264
265    #[test]
266    fn test_false() {
267        Python::attach(|py| {
268            assert!(!PyBool::new(py, false).is_true());
269            let t = PyBool::new(py, false);
270            assert!(!t.extract::<bool>().unwrap());
271            assert!(
272                false
273                    .into_pyobject(py)
274                    .unwrap()
275                    .is(&*PyBool::new(py, false))
276            );
277        });
278    }
279
280    #[test]
281    fn test_pybool_comparisons() {
282        Python::attach(|py| {
283            let py_bool = PyBool::new(py, true);
284            let py_bool_false = PyBool::new(py, false);
285            let rust_bool = true;
286
287            // Bound<'_, PyBool> == bool
288            assert_eq!(*py_bool, rust_bool);
289            assert_ne!(*py_bool_false, rust_bool);
290
291            // Bound<'_, PyBool> == &bool
292            assert_eq!(*py_bool, &rust_bool);
293            assert_ne!(*py_bool_false, &rust_bool);
294
295            // &Bound<'_, PyBool> == bool
296            assert_eq!(&*py_bool, rust_bool);
297            assert_ne!(&*py_bool_false, rust_bool);
298
299            // &Bound<'_, PyBool> == &bool
300            assert_eq!(&*py_bool, &rust_bool);
301            assert_ne!(&*py_bool_false, &rust_bool);
302
303            // bool == Bound<'_, PyBool>
304            assert_eq!(rust_bool, *py_bool);
305            assert_ne!(rust_bool, *py_bool_false);
306
307            // bool == &Bound<'_, PyBool>
308            assert_eq!(rust_bool, &*py_bool);
309            assert_ne!(rust_bool, &*py_bool_false);
310
311            // &bool == Bound<'_, PyBool>
312            assert_eq!(&rust_bool, *py_bool);
313            assert_ne!(&rust_bool, *py_bool_false);
314
315            // &bool == &Bound<'_, PyBool>
316            assert_eq!(&rust_bool, &*py_bool);
317            assert_ne!(&rust_bool, &*py_bool_false);
318
319            // Borrowed<'_, '_, PyBool> == bool
320            assert_eq!(py_bool, rust_bool);
321            assert_ne!(py_bool_false, rust_bool);
322
323            // Borrowed<'_, '_, PyBool> == &bool
324            assert_eq!(py_bool, &rust_bool);
325            assert_ne!(py_bool_false, &rust_bool);
326
327            // bool == Borrowed<'_, '_, PyBool>
328            assert_eq!(rust_bool, py_bool);
329            assert_ne!(rust_bool, py_bool_false);
330
331            // &bool == Borrowed<'_, '_, PyBool>
332            assert_eq!(&rust_bool, py_bool);
333            assert_ne!(&rust_bool, py_bool_false);
334            assert_eq!(py_bool, rust_bool);
335            assert_ne!(py_bool_false, rust_bool);
336        })
337    }
338}