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                .cast_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    #[cfg(feature = "experimental-inspect")]
145    const OUTPUT_TYPE: &'static str = "bool";
146
147    #[inline]
148    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
149        Ok(PyBool::new(py, self))
150    }
151
152    #[cfg(feature = "experimental-inspect")]
153    fn type_output() -> TypeInfo {
154        TypeInfo::builtin("bool")
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: &'static str = bool::OUTPUT_TYPE;
165
166    #[inline]
167    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
168        (*self).into_pyobject(py)
169    }
170
171    #[cfg(feature = "experimental-inspect")]
172    fn type_output() -> TypeInfo {
173        TypeInfo::builtin("bool")
174    }
175}
176
177/// Converts a Python `bool` to a Rust `bool`.
178///
179/// Fails with `TypeError` if the input is not a Python `bool`.
180impl FromPyObject<'_> for bool {
181    #[cfg(feature = "experimental-inspect")]
182    const INPUT_TYPE: &'static str = "bool";
183
184    fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
185        let err = match obj.cast::<PyBool>() {
186            Ok(obj) => return Ok(obj.is_true()),
187            Err(err) => err,
188        };
189
190        let is_numpy_bool = {
191            let ty = obj.get_type();
192            ty.module().is_ok_and(|module| module == "numpy")
193                && ty
194                    .name()
195                    .is_ok_and(|name| name == "bool_" || name == "bool")
196        };
197
198        if is_numpy_bool {
199            let missing_conversion = |obj: &Bound<'_, PyAny>| {
200                PyTypeError::new_err(format!(
201                    "object of type '{}' does not define a '__bool__' conversion",
202                    obj.get_type()
203                ))
204            };
205
206            #[cfg(not(any(Py_LIMITED_API, PyPy)))]
207            unsafe {
208                let ptr = obj.as_ptr();
209
210                if let Some(tp_as_number) = (*(*ptr).ob_type).tp_as_number.as_ref() {
211                    if let Some(nb_bool) = tp_as_number.nb_bool {
212                        match (nb_bool)(ptr) {
213                            0 => return Ok(false),
214                            1 => return Ok(true),
215                            _ => return Err(crate::PyErr::fetch(obj.py())),
216                        }
217                    }
218                }
219
220                return Err(missing_conversion(obj));
221            }
222
223            #[cfg(any(Py_LIMITED_API, PyPy))]
224            {
225                let meth = obj
226                    .lookup_special(crate::intern!(obj.py(), "__bool__"))?
227                    .ok_or_else(|| missing_conversion(obj))?;
228
229                let obj = meth.call0()?.cast_into::<PyBool>()?;
230                return Ok(obj.is_true());
231            }
232        }
233
234        Err(err.into())
235    }
236
237    #[cfg(feature = "experimental-inspect")]
238    fn type_input() -> TypeInfo {
239        Self::type_output()
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use crate::types::any::PyAnyMethods;
246    use crate::types::boolobject::PyBoolMethods;
247    use crate::types::PyBool;
248    use crate::IntoPyObject;
249    use crate::Python;
250
251    #[test]
252    fn test_true() {
253        Python::attach(|py| {
254            assert!(PyBool::new(py, true).is_true());
255            let t = PyBool::new(py, true);
256            assert!(t.extract::<bool>().unwrap());
257            assert!(true.into_pyobject(py).unwrap().is(&*PyBool::new(py, true)));
258        });
259    }
260
261    #[test]
262    fn test_false() {
263        Python::attach(|py| {
264            assert!(!PyBool::new(py, false).is_true());
265            let t = PyBool::new(py, false);
266            assert!(!t.extract::<bool>().unwrap());
267            assert!(false
268                .into_pyobject(py)
269                .unwrap()
270                .is(&*PyBool::new(py, false)));
271        });
272    }
273
274    #[test]
275    fn test_pybool_comparisons() {
276        Python::attach(|py| {
277            let py_bool = PyBool::new(py, true);
278            let py_bool_false = PyBool::new(py, false);
279            let rust_bool = true;
280
281            // Bound<'_, PyBool> == bool
282            assert_eq!(*py_bool, rust_bool);
283            assert_ne!(*py_bool_false, rust_bool);
284
285            // Bound<'_, PyBool> == &bool
286            assert_eq!(*py_bool, &rust_bool);
287            assert_ne!(*py_bool_false, &rust_bool);
288
289            // &Bound<'_, PyBool> == bool
290            assert_eq!(&*py_bool, rust_bool);
291            assert_ne!(&*py_bool_false, rust_bool);
292
293            // &Bound<'_, PyBool> == &bool
294            assert_eq!(&*py_bool, &rust_bool);
295            assert_ne!(&*py_bool_false, &rust_bool);
296
297            // bool == Bound<'_, PyBool>
298            assert_eq!(rust_bool, *py_bool);
299            assert_ne!(rust_bool, *py_bool_false);
300
301            // bool == &Bound<'_, PyBool>
302            assert_eq!(rust_bool, &*py_bool);
303            assert_ne!(rust_bool, &*py_bool_false);
304
305            // &bool == Bound<'_, PyBool>
306            assert_eq!(&rust_bool, *py_bool);
307            assert_ne!(&rust_bool, *py_bool_false);
308
309            // &bool == &Bound<'_, PyBool>
310            assert_eq!(&rust_bool, &*py_bool);
311            assert_ne!(&rust_bool, &*py_bool_false);
312
313            // Borrowed<'_, '_, PyBool> == bool
314            assert_eq!(py_bool, rust_bool);
315            assert_ne!(py_bool_false, rust_bool);
316
317            // Borrowed<'_, '_, PyBool> == &bool
318            assert_eq!(py_bool, &rust_bool);
319            assert_ne!(py_bool_false, &rust_bool);
320
321            // bool == Borrowed<'_, '_, PyBool>
322            assert_eq!(rust_bool, py_bool);
323            assert_ne!(rust_bool, py_bool_false);
324
325            // &bool == Borrowed<'_, '_, PyBool>
326            assert_eq!(&rust_bool, py_bool);
327            assert_ne!(&rust_bool, py_bool_false);
328            assert_eq!(py_bool, rust_bool);
329            assert_ne!(py_bool_false, rust_bool);
330        })
331    }
332}