diff --git a/newsfragments/5402.added.md b/newsfragments/5402.added.md new file mode 100644 index 00000000000..88ff4a9c589 --- /dev/null +++ b/newsfragments/5402.added.md @@ -0,0 +1 @@ +Implement `PyTypeInfo` on `PyIterator`, `PyMapping` and `PySequence` \ No newline at end of file diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 5424c8849c4..2d300c5d50e 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -1,7 +1,9 @@ use crate::ffi_ptr_ext::FfiPtrExt; use crate::instance::Borrowed; use crate::py_result_ext::PyResultExt; -use crate::{ffi, Bound, PyAny, PyErr, PyResult, PyTypeCheck}; +use crate::sync::PyOnceLock; +use crate::types::{PyType, PyTypeMethods}; +use crate::{ffi, Bound, Py, PyAny, PyErr, PyResult}; /// A Python iterator object. /// @@ -29,7 +31,18 @@ use crate::{ffi, Bound, PyAny, PyErr, PyResult, PyTypeCheck}; /// ``` #[repr(transparent)] pub struct PyIterator(PyAny); -pyobject_native_type_named!(PyIterator); + +pyobject_native_type_core!( + PyIterator, + |py| { + static TYPE: PyOnceLock> = PyOnceLock::new(); + TYPE.import(py, "collections.abc", "Iterator") + .unwrap() + .as_type_ptr() + }, + #module=Some("collections.abc"), + #checkfunction=ffi::PyIter_Check +); impl PyIterator { /// Builds an iterator for an iterable Python object; the equivalent of calling `iter(obj)` in Python. @@ -117,16 +130,6 @@ impl<'py> IntoIterator for &Bound<'py, PyIterator> { } } -impl PyTypeCheck for PyIterator { - const NAME: &'static str = "Iterator"; - #[cfg(feature = "experimental-inspect")] - const PYTHON_TYPE: &'static str = "collections.abc.Iterator"; - - fn type_check(object: &Bound<'_, PyAny>) -> bool { - unsafe { ffi::PyIter_Check(object.as_ptr()) != 0 } - } -} - #[cfg(test)] mod tests { use super::PyIterator; @@ -136,7 +139,7 @@ mod tests { #[cfg(all(not(PyPy), Py_3_10))] use crate::types::PyNone; use crate::types::{PyAnyMethods, PyDict, PyList, PyListMethods}; - use crate::{ffi, IntoPyObject, Python}; + use crate::{ffi, IntoPyObject, PyTypeInfo, Python}; #[test] fn vec_iter() { @@ -351,7 +354,7 @@ def fibonacci(target): assert_eq!( downcaster.borrow_mut(py).failed.take().unwrap().to_string(), - "TypeError: 'MySequence' object cannot be converted to 'Iterator'" + "TypeError: 'MySequence' object cannot be converted to 'PyIterator'" ); }); } @@ -391,4 +394,13 @@ def fibonacci(target): assert_eq!(hint, (3, None)); }); } + + #[test] + fn test_type_object() { + Python::attach(|py| { + let abc = PyIterator::type_object(py); + let iter = py.eval(ffi::c_str!("iter(())"), None, None).unwrap(); + assert!(iter.is_instance(&abc).unwrap()); + }) + } } diff --git a/src/types/mapping.rs b/src/types/mapping.rs index 8887f19db9e..af5b26ba463 100644 --- a/src/types/mapping.rs +++ b/src/types/mapping.rs @@ -6,8 +6,8 @@ use crate::py_result_ext::PyResultExt; use crate::sync::PyOnceLock; use crate::type_object::PyTypeInfo; use crate::types::any::PyAnyMethods; -use crate::types::{PyAny, PyDict, PyList, PyType}; -use crate::{ffi, Py, PyTypeCheck, Python}; +use crate::types::{PyAny, PyDict, PyList, PyType, PyTypeMethods}; +use crate::{ffi, Py, Python}; /// Represents a reference to a Python object supporting the mapping protocol. /// @@ -18,15 +18,43 @@ use crate::{ffi, Py, PyTypeCheck, Python}; /// [`Bound<'py, PyMapping>`][Bound]. #[repr(transparent)] pub struct PyMapping(PyAny); + pyobject_native_type_named!(PyMapping); +unsafe impl PyTypeInfo for PyMapping { + const NAME: &'static str = "Mapping"; + const MODULE: Option<&'static str> = Some("collections.abc"); + + #[inline] + #[allow(clippy::redundant_closure_call)] + fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject { + static TYPE: PyOnceLock> = PyOnceLock::new(); + TYPE.import(py, "collections.abc", "Mapping") + .unwrap() + .as_type_ptr() + } + + #[inline] + fn is_type_of(object: &Bound<'_, PyAny>) -> bool { + // Using `is_instance` for `collections.abc.Mapping` is slow, so provide + // optimized case dict as a well-known mapping + PyDict::is_type_of(object) + || object + .is_instance(&Self::type_object(object.py()).into_any()) + .unwrap_or_else(|err| { + err.write_unraisable(object.py(), Some(object)); + false + }) + } +} + impl PyMapping { /// Register a pyclass as a subclass of `collections.abc.Mapping` (from the Python standard /// library). This is equivalent to `collections.abc.Mapping.register(T)` in Python. /// This registration is required for a pyclass to be castable from `PyAny` to `PyMapping`. pub fn register(py: Python<'_>) -> PyResult<()> { let ty = T::type_object(py); - get_mapping_abc(py)?.call_method1("register", (ty,))?; + Self::type_object(py).call_method1("register", (ty,))?; Ok(()) } } @@ -160,31 +188,6 @@ impl<'py> PyMappingMethods<'py> for Bound<'py, PyMapping> { } } -fn get_mapping_abc(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> { - static MAPPING_ABC: PyOnceLock> = PyOnceLock::new(); - - MAPPING_ABC.import(py, "collections.abc", "Mapping") -} - -impl PyTypeCheck for PyMapping { - const NAME: &'static str = "Mapping"; - #[cfg(feature = "experimental-inspect")] - const PYTHON_TYPE: &'static str = "collections.abc.Mapping"; - - #[inline] - fn type_check(object: &Bound<'_, PyAny>) -> bool { - // Using `is_instance` for `collections.abc.Mapping` is slow, so provide - // optimized case dict as a well-known mapping - PyDict::is_type_of(object) - || get_mapping_abc(object.py()) - .and_then(|abc| object.is_instance(abc)) - .unwrap_or_else(|err| { - err.write_unraisable(object.py(), Some(object)); - false - }) - } -} - #[cfg(test)] mod tests { use std::collections::HashMap; @@ -337,4 +340,12 @@ mod tests { assert_eq!(32 + 42 + 123, values_sum); }); } + + #[test] + fn test_type_object() { + Python::attach(|py| { + let abc = PyMapping::type_object(py); + assert!(PyDict::new(py).is_instance(&abc).unwrap()); + }) + } } diff --git a/src/types/sequence.rs b/src/types/sequence.rs index fb3d661e76d..ba82ef350ca 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -9,11 +9,8 @@ use crate::internal_tricks::get_ssize_index; use crate::py_result_ext::PyResultExt; use crate::sync::PyOnceLock; use crate::type_object::PyTypeInfo; -use crate::types::{any::PyAnyMethods, PyAny, PyList, PyString, PyTuple, PyType}; -use crate::{ - ffi, Borrowed, BoundObject, FromPyObject, IntoPyObject, IntoPyObjectExt, Py, PyTypeCheck, - Python, -}; +use crate::types::{any::PyAnyMethods, PyAny, PyList, PyString, PyTuple, PyType, PyTypeMethods}; +use crate::{ffi, Borrowed, BoundObject, FromPyObject, IntoPyObject, IntoPyObjectExt, Py, Python}; /// Represents a reference to a Python object supporting the sequence protocol. /// @@ -24,15 +21,44 @@ use crate::{ /// [`Bound<'py, PySequence>`][Bound]. #[repr(transparent)] pub struct PySequence(PyAny); + pyobject_native_type_named!(PySequence); +unsafe impl PyTypeInfo for PySequence { + const NAME: &'static str = "Sequence"; + const MODULE: Option<&'static str> = Some("collections.abc"); + + #[inline] + #[allow(clippy::redundant_closure_call)] + fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject { + static TYPE: PyOnceLock> = PyOnceLock::new(); + TYPE.import(py, "collections.abc", "Sequence") + .unwrap() + .as_type_ptr() + } + + #[inline] + fn is_type_of(object: &Bound<'_, PyAny>) -> bool { + // Using `is_instance` for `collections.abc.Sequence` is slow, so provide + // optimized cases for list and tuples as common well-known sequences + PyList::is_type_of(object) + || PyTuple::is_type_of(object) + || object + .is_instance(&Self::type_object(object.py()).into_any()) + .unwrap_or_else(|err| { + err.write_unraisable(object.py(), Some(object)); + false + }) + } +} + impl PySequence { /// Register a pyclass as a subclass of `collections.abc.Sequence` (from the Python standard /// library). This is equivalent to `collections.abc.Sequence.register(T)` in Python. /// This registration is required for a pyclass to be castable from `PyAny` to `PySequence`. pub fn register(py: Python<'_>) -> PyResult<()> { let ty = T::type_object(py); - get_sequence_abc(py)?.call_method1("register", (ty,))?; + Self::type_object(py).call_method1("register", (ty,))?; Ok(()) } } @@ -372,36 +398,10 @@ where Ok(v) } -fn get_sequence_abc(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> { - static SEQUENCE_ABC: PyOnceLock> = PyOnceLock::new(); - - SEQUENCE_ABC.import(py, "collections.abc", "Sequence") -} - -impl PyTypeCheck for PySequence { - const NAME: &'static str = "Sequence"; - #[cfg(feature = "experimental-inspect")] - const PYTHON_TYPE: &'static str = "collections.abc.Sequence"; - - #[inline] - fn type_check(object: &Bound<'_, PyAny>) -> bool { - // Using `is_instance` for `collections.abc.Sequence` is slow, so provide - // optimized cases for list and tuples as common well-known sequences - PyList::is_type_of(object) - || PyTuple::is_type_of(object) - || get_sequence_abc(object.py()) - .and_then(|abc| object.is_instance(abc)) - .unwrap_or_else(|err| { - err.write_unraisable(object.py(), Some(object)); - false - }) - } -} - #[cfg(test)] mod tests { use crate::types::{PyAnyMethods, PyList, PySequence, PySequenceMethods, PyTuple}; - use crate::{ffi, IntoPyObject, Py, PyAny, Python}; + use crate::{ffi, IntoPyObject, Py, PyAny, PyTypeInfo, Python}; use std::ptr; fn get_object() -> Py { @@ -827,4 +827,13 @@ mod tests { assert!(seq_from.to_list().is_ok()); }); } + + #[test] + fn test_type_object() { + Python::attach(|py| { + let abc = PySequence::type_object(py); + assert!(PyList::empty(py).is_instance(&abc).unwrap()); + assert!(PyTuple::empty(py).is_instance(&abc).unwrap()); + }) + } }