pyo3/types/
boolobject.rs

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