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