pyo3/types/
boolobject.rs

1#[cfg(feature = "experimental-inspect")]
2use crate::inspect::types::TypeInfo;
3#[cfg(feature = "gil-refs")]
4use crate::PyNativeType;
5use crate::{
6    exceptions::PyTypeError, ffi, ffi_ptr_ext::FfiPtrExt, instance::Bound,
7    types::typeobject::PyTypeMethods, Borrowed, FromPyObject, IntoPy, PyAny, PyObject, PyResult,
8    Python, ToPyObject,
9};
10
11use super::any::PyAnyMethods;
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_bound(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#[cfg(feature = "gil-refs")]
42impl PyBool {
43    /// Deprecated form of [`PyBool::new_bound`]
44    #[deprecated(
45        since = "0.21.0",
46        note = "`PyBool::new` will be replaced by `PyBool::new_bound` in a future PyO3 version"
47    )]
48    #[inline]
49    pub fn new(py: Python<'_>, val: bool) -> &PyBool {
50        #[allow(deprecated)]
51        unsafe {
52            py.from_borrowed_ptr(if val { ffi::Py_True() } else { ffi::Py_False() })
53        }
54    }
55
56    /// Gets whether this boolean is `true`.
57    #[inline]
58    pub fn is_true(&self) -> bool {
59        self.as_borrowed().is_true()
60    }
61}
62
63/// Implementation of functionality for [`PyBool`].
64///
65/// These methods are defined for the `Bound<'py, PyBool>` smart pointer, so to use method call
66/// syntax these methods are separated into a trait, because stable Rust does not yet support
67/// `arbitrary_self_types`.
68#[doc(alias = "PyBool")]
69pub trait PyBoolMethods<'py>: crate::sealed::Sealed {
70    /// Gets whether this boolean is `true`.
71    fn is_true(&self) -> bool;
72}
73
74impl<'py> PyBoolMethods<'py> for Bound<'py, PyBool> {
75    #[inline]
76    fn is_true(&self) -> bool {
77        self.as_ptr() == unsafe { crate::ffi::Py_True() }
78    }
79}
80
81/// Compare `Bound<PyBool>` with `bool`.
82impl PartialEq<bool> for Bound<'_, PyBool> {
83    #[inline]
84    fn eq(&self, other: &bool) -> bool {
85        self.as_borrowed() == *other
86    }
87}
88
89/// Compare `&Bound<PyBool>` with `bool`.
90impl PartialEq<bool> for &'_ Bound<'_, PyBool> {
91    #[inline]
92    fn eq(&self, other: &bool) -> bool {
93        self.as_borrowed() == *other
94    }
95}
96
97/// Compare `Bound<PyBool>` with `&bool`.
98impl PartialEq<&'_ bool> for Bound<'_, PyBool> {
99    #[inline]
100    fn eq(&self, other: &&bool) -> bool {
101        self.as_borrowed() == **other
102    }
103}
104
105/// Compare `bool` with `Bound<PyBool>`
106impl PartialEq<Bound<'_, PyBool>> for bool {
107    #[inline]
108    fn eq(&self, other: &Bound<'_, PyBool>) -> bool {
109        *self == other.as_borrowed()
110    }
111}
112
113/// Compare `bool` with `&Bound<PyBool>`
114impl PartialEq<&'_ Bound<'_, PyBool>> for bool {
115    #[inline]
116    fn eq(&self, other: &&'_ Bound<'_, PyBool>) -> bool {
117        *self == other.as_borrowed()
118    }
119}
120
121/// Compare `&bool` with `Bound<PyBool>`
122impl PartialEq<Bound<'_, PyBool>> for &'_ bool {
123    #[inline]
124    fn eq(&self, other: &Bound<'_, PyBool>) -> bool {
125        **self == other.as_borrowed()
126    }
127}
128
129/// Compare `Borrowed<PyBool>` with `bool`
130impl PartialEq<bool> for Borrowed<'_, '_, PyBool> {
131    #[inline]
132    fn eq(&self, other: &bool) -> bool {
133        self.is_true() == *other
134    }
135}
136
137/// Compare `Borrowed<PyBool>` with `&bool`
138impl PartialEq<&bool> for Borrowed<'_, '_, PyBool> {
139    #[inline]
140    fn eq(&self, other: &&bool) -> bool {
141        self.is_true() == **other
142    }
143}
144
145/// Compare `bool` with `Borrowed<PyBool>`
146impl PartialEq<Borrowed<'_, '_, PyBool>> for bool {
147    #[inline]
148    fn eq(&self, other: &Borrowed<'_, '_, PyBool>) -> bool {
149        *self == other.is_true()
150    }
151}
152
153/// Compare `&bool` with `Borrowed<PyBool>`
154impl PartialEq<Borrowed<'_, '_, PyBool>> for &'_ bool {
155    #[inline]
156    fn eq(&self, other: &Borrowed<'_, '_, PyBool>) -> bool {
157        **self == other.is_true()
158    }
159}
160
161/// Converts a Rust `bool` to a Python `bool`.
162impl ToPyObject for bool {
163    #[inline]
164    fn to_object(&self, py: Python<'_>) -> PyObject {
165        unsafe {
166            PyObject::from_borrowed_ptr(
167                py,
168                if *self {
169                    ffi::Py_True()
170                } else {
171                    ffi::Py_False()
172                },
173            )
174        }
175    }
176}
177
178impl IntoPy<PyObject> for bool {
179    #[inline]
180    fn into_py(self, py: Python<'_>) -> PyObject {
181        PyBool::new_bound(py, self).into_py(py)
182    }
183
184    #[cfg(feature = "experimental-inspect")]
185    fn type_output() -> TypeInfo {
186        TypeInfo::builtin("bool")
187    }
188}
189
190/// Converts a Python `bool` to a Rust `bool`.
191///
192/// Fails with `TypeError` if the input is not a Python `bool`.
193impl FromPyObject<'_> for bool {
194    fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
195        let err = match obj.downcast::<PyBool>() {
196            Ok(obj) => return Ok(obj.is_true()),
197            Err(err) => err,
198        };
199
200        let is_numpy_bool = {
201            let ty = obj.get_type();
202            ty.module().map_or(false, |module| module == "numpy")
203                && ty
204                    .name()
205                    .map_or(false, |name| name == "bool_" || name == "bool")
206        };
207
208        if is_numpy_bool {
209            let missing_conversion = |obj: &Bound<'_, PyAny>| {
210                PyTypeError::new_err(format!(
211                    "object of type '{}' does not define a '__bool__' conversion",
212                    obj.get_type()
213                ))
214            };
215
216            #[cfg(not(any(Py_LIMITED_API, PyPy)))]
217            unsafe {
218                let ptr = obj.as_ptr();
219
220                if let Some(tp_as_number) = (*(*ptr).ob_type).tp_as_number.as_ref() {
221                    if let Some(nb_bool) = tp_as_number.nb_bool {
222                        match (nb_bool)(ptr) {
223                            0 => return Ok(false),
224                            1 => return Ok(true),
225                            _ => return Err(crate::PyErr::fetch(obj.py())),
226                        }
227                    }
228                }
229
230                return Err(missing_conversion(obj));
231            }
232
233            #[cfg(any(Py_LIMITED_API, PyPy))]
234            {
235                let meth = obj
236                    .lookup_special(crate::intern!(obj.py(), "__bool__"))?
237                    .ok_or_else(|| missing_conversion(obj))?;
238
239                let obj = meth.call0()?.downcast_into::<PyBool>()?;
240                return Ok(obj.is_true());
241            }
242        }
243
244        Err(err.into())
245    }
246
247    #[cfg(feature = "experimental-inspect")]
248    fn type_input() -> TypeInfo {
249        Self::type_output()
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use crate::types::any::PyAnyMethods;
256    use crate::types::boolobject::PyBoolMethods;
257    use crate::types::PyBool;
258    use crate::Python;
259    use crate::ToPyObject;
260
261    #[test]
262    fn test_true() {
263        Python::with_gil(|py| {
264            assert!(PyBool::new_bound(py, true).is_true());
265            let t = PyBool::new_bound(py, true);
266            assert!(t.extract::<bool>().unwrap());
267            assert!(true.to_object(py).is(&*PyBool::new_bound(py, true)));
268        });
269    }
270
271    #[test]
272    fn test_false() {
273        Python::with_gil(|py| {
274            assert!(!PyBool::new_bound(py, false).is_true());
275            let t = PyBool::new_bound(py, false);
276            assert!(!t.extract::<bool>().unwrap());
277            assert!(false.to_object(py).is(&*PyBool::new_bound(py, false)));
278        });
279    }
280
281    #[test]
282    fn test_pybool_comparisons() {
283        Python::with_gil(|py| {
284            let py_bool = PyBool::new_bound(py, true);
285            let py_bool_false = PyBool::new_bound(py, false);
286            let rust_bool = true;
287
288            // Bound<'_, PyBool> == bool
289            assert_eq!(*py_bool, rust_bool);
290            assert_ne!(*py_bool_false, rust_bool);
291
292            // Bound<'_, PyBool> == &bool
293            assert_eq!(*py_bool, &rust_bool);
294            assert_ne!(*py_bool_false, &rust_bool);
295
296            // &Bound<'_, PyBool> == bool
297            assert_eq!(&*py_bool, rust_bool);
298            assert_ne!(&*py_bool_false, rust_bool);
299
300            // &Bound<'_, PyBool> == &bool
301            assert_eq!(&*py_bool, &rust_bool);
302            assert_ne!(&*py_bool_false, &rust_bool);
303
304            // bool == Bound<'_, PyBool>
305            assert_eq!(rust_bool, *py_bool);
306            assert_ne!(rust_bool, *py_bool_false);
307
308            // bool == &Bound<'_, PyBool>
309            assert_eq!(rust_bool, &*py_bool);
310            assert_ne!(rust_bool, &*py_bool_false);
311
312            // &bool == Bound<'_, PyBool>
313            assert_eq!(&rust_bool, *py_bool);
314            assert_ne!(&rust_bool, *py_bool_false);
315
316            // &bool == &Bound<'_, PyBool>
317            assert_eq!(&rust_bool, &*py_bool);
318            assert_ne!(&rust_bool, &*py_bool_false);
319
320            // Borrowed<'_, '_, PyBool> == bool
321            assert_eq!(py_bool, rust_bool);
322            assert_ne!(py_bool_false, rust_bool);
323
324            // Borrowed<'_, '_, PyBool> == &bool
325            assert_eq!(py_bool, &rust_bool);
326            assert_ne!(py_bool_false, &rust_bool);
327
328            // bool == Borrowed<'_, '_, PyBool>
329            assert_eq!(rust_bool, py_bool);
330            assert_ne!(rust_bool, py_bool_false);
331
332            // &bool == Borrowed<'_, '_, PyBool>
333            assert_eq!(&rust_bool, py_bool);
334            assert_ne!(&rust_bool, py_bool_false);
335            assert_eq!(py_bool, rust_bool);
336            assert_ne!(py_bool_false, rust_bool);
337        })
338    }
339}