Skip to main content

pyo3/types/
boolobject.rs

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