diff --git a/newsfragments/3408.added.md b/newsfragments/3408.added.md new file mode 100644 index 00000000..db1a1efe --- /dev/null +++ b/newsfragments/3408.added.md @@ -0,0 +1 @@ +Add types for `None`, `Ellipsis`, and `NotImplemented` diff --git a/src/conversion.rs b/src/conversion.rs index 5e6211bd..537aff56 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -262,19 +262,6 @@ where } } -/// `()` is converted to Python `None`. -impl ToPyObject for () { - fn to_object(&self, py: Python<'_>) -> PyObject { - py.None() - } -} - -impl IntoPy for () { - fn into_py(self, py: Python<'_>) -> PyObject { - py.None() - } -} - impl IntoPy for &'_ PyAny { #[inline] fn into_py(self, py: Python<'_>) -> PyObject { diff --git a/src/marker.rs b/src/marker.rs index 58a1104d..2d130ae4 100644 --- a/src/marker.rs +++ b/src/marker.rs @@ -118,7 +118,9 @@ use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::gil::{GILGuard, GILPool, SuspendGIL}; use crate::impl_::not_send::NotSend; -use crate::types::{PyAny, PyDict, PyModule, PyString, PyType}; +use crate::types::{ + PyAny, PyDict, PyEllipsis, PyModule, PyNone, PyNotImplemented, PyString, PyType, +}; use crate::version::PythonVersionInfo; use crate::{ffi, FromPyPointer, IntoPy, Py, PyNativeType, PyObject, PyTryFrom, PyTypeInfo}; use std::ffi::{CStr, CString}; @@ -690,21 +692,21 @@ impl<'py> Python<'py> { #[allow(non_snake_case)] // the Python keyword starts with uppercase #[inline] pub fn None(self) -> PyObject { - unsafe { PyObject::from_borrowed_ptr(self, ffi::Py_None()) } + PyNone::get(self).into() } /// Gets the Python builtin value `Ellipsis`, or `...`. #[allow(non_snake_case)] // the Python keyword starts with uppercase #[inline] pub fn Ellipsis(self) -> PyObject { - unsafe { PyObject::from_borrowed_ptr(self, ffi::Py_Ellipsis()) } + PyEllipsis::get(self).into() } /// Gets the Python builtin value `NotImplemented`. #[allow(non_snake_case)] // the Python keyword starts with uppercase #[inline] pub fn NotImplemented(self) -> PyObject { - unsafe { PyObject::from_borrowed_ptr(self, ffi::Py_NotImplemented()) } + PyNotImplemented::get(self).into() } /// Gets the running Python interpreter version as a string. diff --git a/src/types/ellipsis.rs b/src/types/ellipsis.rs new file mode 100644 index 00000000..f0603b66 --- /dev/null +++ b/src/types/ellipsis.rs @@ -0,0 +1,60 @@ +use crate::{ffi, PyAny, PyDowncastError, PyTryFrom, Python}; + +/// Represents the Python `Ellipsis` object. +#[repr(transparent)] +pub struct PyEllipsis(PyAny); + +pyobject_native_type_named!(PyEllipsis); +pyobject_native_type_extract!(PyEllipsis); + +impl PyEllipsis { + /// Returns the `Ellipsis` object. + #[inline] + pub fn get(py: Python<'_>) -> &PyEllipsis { + unsafe { py.from_borrowed_ptr(ffi::Py_Ellipsis()) } + } +} + +impl<'v> PyTryFrom<'v> for PyEllipsis { + fn try_from>(value: V) -> Result<&'v Self, crate::PyDowncastError<'v>> { + let value: &PyAny = value.into(); + if unsafe { ffi::Py_Ellipsis() == value.as_ptr() } { + return unsafe { Ok(value.downcast_unchecked()) }; + } + Err(PyDowncastError::new(value, "ellipsis")) + } + + fn try_from_exact>( + value: V, + ) -> Result<&'v Self, crate::PyDowncastError<'v>> { + value.into().downcast() + } + + unsafe fn try_from_unchecked>(value: V) -> &'v Self { + let ptr = value.into() as *const _ as *const PyEllipsis; + &*ptr + } +} + +#[cfg(test)] +mod tests { + use crate::types::{PyDict, PyEllipsis}; + use crate::Python; + + #[test] + fn test_ellipsis_is_itself() { + Python::with_gil(|py| { + assert!(PyEllipsis::get(py) + .downcast::() + .unwrap() + .is_ellipsis()); + }) + } + + #[test] + fn test_dict_is_not_ellipsis() { + Python::with_gil(|py| { + assert!(PyDict::new(py).downcast::().is_err()); + }) + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 4d8b0513..dc6e6d69 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -16,6 +16,7 @@ pub use self::datetime::{ pub use self::dict::{IntoPyDict, PyDict}; #[cfg(not(PyPy))] pub use self::dict::{PyDictItems, PyDictKeys, PyDictValues}; +pub use self::ellipsis::PyEllipsis; pub use self::floatob::PyFloat; #[cfg(all(not(Py_LIMITED_API), not(PyPy)))] pub use self::frame::PyFrame; @@ -27,6 +28,8 @@ pub use self::iterator::PyIterator; pub use self::list::PyList; pub use self::mapping::PyMapping; pub use self::module::PyModule; +pub use self::none::PyNone; +pub use self::notimplemented::PyNotImplemented; pub use self::num::PyLong; pub use self::num::PyLong as PyInt; #[cfg(not(PyPy))] @@ -278,6 +281,7 @@ mod complex; #[cfg(not(Py_LIMITED_API))] mod datetime; mod dict; +mod ellipsis; mod floatob; #[cfg(all(not(Py_LIMITED_API), not(PyPy)))] mod frame; @@ -287,6 +291,8 @@ mod iterator; pub(crate) mod list; mod mapping; mod module; +mod none; +mod notimplemented; mod num; #[cfg(not(PyPy))] mod pysuper; diff --git a/src/types/none.rs b/src/types/none.rs new file mode 100644 index 00000000..d80faac2 --- /dev/null +++ b/src/types/none.rs @@ -0,0 +1,86 @@ +use crate::{ffi, IntoPy, PyAny, PyDowncastError, PyObject, PyTryFrom, Python, ToPyObject}; + +/// Represents the Python `None` object. +#[repr(transparent)] +pub struct PyNone(PyAny); + +pyobject_native_type_named!(PyNone); +pyobject_native_type_extract!(PyNone); + +impl PyNone { + /// Returns the `None` object. + #[inline] + pub fn get(py: Python<'_>) -> &PyNone { + unsafe { py.from_borrowed_ptr(ffi::Py_None()) } + } +} + +impl<'v> PyTryFrom<'v> for PyNone { + fn try_from>(value: V) -> Result<&'v Self, crate::PyDowncastError<'v>> { + let value: &PyAny = value.into(); + if value.is_none() { + return unsafe { Ok(value.downcast_unchecked()) }; + } + Err(PyDowncastError::new(value, "NoneType")) + } + + fn try_from_exact>( + value: V, + ) -> Result<&'v Self, crate::PyDowncastError<'v>> { + value.into().downcast() + } + + unsafe fn try_from_unchecked>(value: V) -> &'v Self { + let ptr = value.into() as *const _ as *const PyNone; + &*ptr + } +} + +/// `()` is converted to Python `None`. +impl ToPyObject for () { + fn to_object(&self, py: Python<'_>) -> PyObject { + PyNone::get(py).into() + } +} + +impl IntoPy for () { + #[inline] + fn into_py(self, py: Python<'_>) -> PyObject { + PyNone::get(py).into() + } +} + +#[cfg(test)] +mod tests { + use crate::types::{PyDict, PyNone}; + use crate::{IntoPy, PyObject, Python, ToPyObject}; + + #[test] + fn test_none_is_none() { + Python::with_gil(|py| { + assert!(PyNone::get(py).downcast::().unwrap().is_none()); + }) + } + + #[test] + fn test_unit_to_object_is_none() { + Python::with_gil(|py| { + assert!(().to_object(py).downcast::(py).is_ok()); + }) + } + + #[test] + fn test_unit_into_py_is_none() { + Python::with_gil(|py| { + let obj: PyObject = ().into_py(py); + assert!(obj.downcast::(py).is_ok()); + }) + } + + #[test] + fn test_dict_is_not_none() { + Python::with_gil(|py| { + assert!(PyDict::new(py).downcast::().is_err()); + }) + } +} diff --git a/src/types/notimplemented.rs b/src/types/notimplemented.rs new file mode 100644 index 00000000..2f08f945 --- /dev/null +++ b/src/types/notimplemented.rs @@ -0,0 +1,60 @@ +use crate::{ffi, PyAny, PyDowncastError, PyTryFrom, Python}; + +/// Represents the Python `NotImplemented` object. +#[repr(transparent)] +pub struct PyNotImplemented(PyAny); + +pyobject_native_type_named!(PyNotImplemented); +pyobject_native_type_extract!(PyNotImplemented); + +impl PyNotImplemented { + /// Returns the `NotImplemented` object. + #[inline] + pub fn get(py: Python<'_>) -> &PyNotImplemented { + unsafe { py.from_borrowed_ptr(ffi::Py_NotImplemented()) } + } +} + +impl<'v> PyTryFrom<'v> for PyNotImplemented { + fn try_from>(value: V) -> Result<&'v Self, crate::PyDowncastError<'v>> { + let value: &PyAny = value.into(); + if unsafe { ffi::Py_NotImplemented() == value.as_ptr() } { + return unsafe { Ok(value.downcast_unchecked()) }; + } + Err(PyDowncastError::new(value, "NotImplementedType")) + } + + fn try_from_exact>( + value: V, + ) -> Result<&'v Self, crate::PyDowncastError<'v>> { + value.into().downcast() + } + + unsafe fn try_from_unchecked>(value: V) -> &'v Self { + let ptr = value.into() as *const _ as *const PyNotImplemented; + &*ptr + } +} + +#[cfg(test)] +mod tests { + use crate::types::{PyDict, PyNotImplemented}; + use crate::Python; + + #[test] + fn test_notimplemented_is_itself() { + Python::with_gil(|py| { + assert!(PyNotImplemented::get(py) + .downcast::() + .unwrap() + .is(&py.NotImplemented())); + }) + } + + #[test] + fn test_dict_is_not_notimplemented() { + Python::with_gil(|py| { + assert!(PyDict::new(py).downcast::().is_err()); + }) + } +}