Skip to main content

pyo3/types/
boolobject.rs

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