Skip to content

Commit 6a03c42

Browse files
committed
PyIterator, PyMapping, PySequence: implement PyTypeInfo
Returns the collections.abc type
1 parent c585999 commit 6a03c42

File tree

4 files changed

+108
-75
lines changed

4 files changed

+108
-75
lines changed

newsfragments/5402.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implement `PyTypeInfo` on `PyIterator`, `PyMapping` and `PySequence`

src/types/iterator.rs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use crate::ffi_ptr_ext::FfiPtrExt;
22
use crate::instance::Borrowed;
33
use crate::py_result_ext::PyResultExt;
4-
use crate::{ffi, Bound, PyAny, PyErr, PyResult, PyTypeCheck};
4+
use crate::sync::PyOnceLock;
5+
use crate::types::{PyType, PyTypeMethods};
6+
use crate::{ffi, Bound, Py, PyAny, PyErr, PyResult};
57

68
/// A Python iterator object.
79
///
@@ -29,7 +31,18 @@ use crate::{ffi, Bound, PyAny, PyErr, PyResult, PyTypeCheck};
2931
/// ```
3032
#[repr(transparent)]
3133
pub struct PyIterator(PyAny);
32-
pyobject_native_type_named!(PyIterator);
34+
35+
pyobject_native_type_core!(
36+
PyIterator,
37+
|py| {
38+
static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
39+
TYPE.import(py, "collections.abc", "Iterator")
40+
.unwrap()
41+
.as_type_ptr()
42+
},
43+
#module=Some("collections.abc"),
44+
#checkfunction=ffi::PyIter_Check
45+
);
3346

3447
impl PyIterator {
3548
/// 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> {
117130
}
118131
}
119132

120-
impl PyTypeCheck for PyIterator {
121-
const NAME: &'static str = "Iterator";
122-
#[cfg(feature = "experimental-inspect")]
123-
const PYTHON_TYPE: &'static str = "collections.abc.Iterator";
124-
125-
fn type_check(object: &Bound<'_, PyAny>) -> bool {
126-
unsafe { ffi::PyIter_Check(object.as_ptr()) != 0 }
127-
}
128-
}
129-
130133
#[cfg(test)]
131134
mod tests {
132135
use super::PyIterator;
@@ -136,7 +139,7 @@ mod tests {
136139
#[cfg(all(not(PyPy), Py_3_10))]
137140
use crate::types::PyNone;
138141
use crate::types::{PyAnyMethods, PyDict, PyList, PyListMethods};
139-
use crate::{ffi, IntoPyObject, Python};
142+
use crate::{ffi, IntoPyObject, PyTypeInfo, Python};
140143

141144
#[test]
142145
fn vec_iter() {
@@ -351,7 +354,7 @@ def fibonacci(target):
351354

352355
assert_eq!(
353356
downcaster.borrow_mut(py).failed.take().unwrap().to_string(),
354-
"TypeError: 'MySequence' object cannot be converted to 'Iterator'"
357+
"TypeError: 'MySequence' object cannot be converted to 'PyIterator'"
355358
);
356359
});
357360
}
@@ -391,4 +394,13 @@ def fibonacci(target):
391394
assert_eq!(hint, (3, None));
392395
});
393396
}
397+
398+
#[test]
399+
fn test_type_object() {
400+
Python::attach(|py| {
401+
let abc = PyIterator::type_object(py);
402+
let iter = py.eval(ffi::c_str!("iter(())"), None, None).unwrap();
403+
assert!(iter.is_instance(&abc).unwrap());
404+
})
405+
}
394406
}

src/types/mapping.rs

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use crate::py_result_ext::PyResultExt;
66
use crate::sync::PyOnceLock;
77
use crate::type_object::PyTypeInfo;
88
use crate::types::any::PyAnyMethods;
9-
use crate::types::{PyAny, PyDict, PyList, PyType};
10-
use crate::{ffi, Py, PyTypeCheck, Python};
9+
use crate::types::{PyAny, PyDict, PyList, PyType, PyTypeMethods};
10+
use crate::{ffi, Py, Python};
1111

1212
/// Represents a reference to a Python object supporting the mapping protocol.
1313
///
@@ -18,15 +18,43 @@ use crate::{ffi, Py, PyTypeCheck, Python};
1818
/// [`Bound<'py, PyMapping>`][Bound].
1919
#[repr(transparent)]
2020
pub struct PyMapping(PyAny);
21+
2122
pyobject_native_type_named!(PyMapping);
2223

24+
unsafe impl PyTypeInfo for PyMapping {
25+
const NAME: &'static str = "Mapping";
26+
const MODULE: Option<&'static str> = Some("collections.abc");
27+
28+
#[inline]
29+
#[allow(clippy::redundant_closure_call)]
30+
fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject {
31+
static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
32+
TYPE.import(py, "collections.abc", "Mapping")
33+
.unwrap()
34+
.as_type_ptr()
35+
}
36+
37+
#[inline]
38+
fn is_type_of(object: &Bound<'_, PyAny>) -> bool {
39+
// Using `is_instance` for `collections.abc.Mapping` is slow, so provide
40+
// optimized case dict as a well-known mapping
41+
PyDict::is_type_of(object)
42+
|| object
43+
.is_instance(&Self::type_object(object.py()).into_any())
44+
.unwrap_or_else(|err| {
45+
err.write_unraisable(object.py(), Some(object));
46+
false
47+
})
48+
}
49+
}
50+
2351
impl PyMapping {
2452
/// Register a pyclass as a subclass of `collections.abc.Mapping` (from the Python standard
2553
/// library). This is equivalent to `collections.abc.Mapping.register(T)` in Python.
2654
/// This registration is required for a pyclass to be castable from `PyAny` to `PyMapping`.
2755
pub fn register<T: PyTypeInfo>(py: Python<'_>) -> PyResult<()> {
2856
let ty = T::type_object(py);
29-
get_mapping_abc(py)?.call_method1("register", (ty,))?;
57+
Self::type_object(py).call_method1("register", (ty,))?;
3058
Ok(())
3159
}
3260
}
@@ -160,31 +188,6 @@ impl<'py> PyMappingMethods<'py> for Bound<'py, PyMapping> {
160188
}
161189
}
162190

163-
fn get_mapping_abc(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
164-
static MAPPING_ABC: PyOnceLock<Py<PyType>> = PyOnceLock::new();
165-
166-
MAPPING_ABC.import(py, "collections.abc", "Mapping")
167-
}
168-
169-
impl PyTypeCheck for PyMapping {
170-
const NAME: &'static str = "Mapping";
171-
#[cfg(feature = "experimental-inspect")]
172-
const PYTHON_TYPE: &'static str = "collections.abc.Mapping";
173-
174-
#[inline]
175-
fn type_check(object: &Bound<'_, PyAny>) -> bool {
176-
// Using `is_instance` for `collections.abc.Mapping` is slow, so provide
177-
// optimized case dict as a well-known mapping
178-
PyDict::is_type_of(object)
179-
|| get_mapping_abc(object.py())
180-
.and_then(|abc| object.is_instance(abc))
181-
.unwrap_or_else(|err| {
182-
err.write_unraisable(object.py(), Some(object));
183-
false
184-
})
185-
}
186-
}
187-
188191
#[cfg(test)]
189192
mod tests {
190193
use std::collections::HashMap;
@@ -337,4 +340,12 @@ mod tests {
337340
assert_eq!(32 + 42 + 123, values_sum);
338341
});
339342
}
343+
344+
#[test]
345+
fn test_type_object() {
346+
Python::attach(|py| {
347+
let abc = PyMapping::type_object(py);
348+
assert!(PyDict::new(py).is_instance(&abc).unwrap());
349+
})
350+
}
340351
}

src/types/sequence.rs

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@ use crate::internal_tricks::get_ssize_index;
99
use crate::py_result_ext::PyResultExt;
1010
use crate::sync::PyOnceLock;
1111
use crate::type_object::PyTypeInfo;
12-
use crate::types::{any::PyAnyMethods, PyAny, PyList, PyString, PyTuple, PyType};
13-
use crate::{
14-
ffi, Borrowed, BoundObject, FromPyObject, IntoPyObject, IntoPyObjectExt, Py, PyTypeCheck,
15-
Python,
16-
};
12+
use crate::types::{any::PyAnyMethods, PyAny, PyList, PyString, PyTuple, PyType, PyTypeMethods};
13+
use crate::{ffi, Borrowed, BoundObject, FromPyObject, IntoPyObject, IntoPyObjectExt, Py, Python};
1714

1815
/// Represents a reference to a Python object supporting the sequence protocol.
1916
///
@@ -24,15 +21,44 @@ use crate::{
2421
/// [`Bound<'py, PySequence>`][Bound].
2522
#[repr(transparent)]
2623
pub struct PySequence(PyAny);
24+
2725
pyobject_native_type_named!(PySequence);
2826

27+
unsafe impl PyTypeInfo for PySequence {
28+
const NAME: &'static str = "Sequence";
29+
const MODULE: Option<&'static str> = Some("collections.abc");
30+
31+
#[inline]
32+
#[allow(clippy::redundant_closure_call)]
33+
fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject {
34+
static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
35+
TYPE.import(py, "collections.abc", "Sequence")
36+
.unwrap()
37+
.as_type_ptr()
38+
}
39+
40+
#[inline]
41+
fn is_type_of(object: &Bound<'_, PyAny>) -> bool {
42+
// Using `is_instance` for `collections.abc.Sequence` is slow, so provide
43+
// optimized cases for list and tuples as common well-known sequences
44+
PyList::is_type_of(object)
45+
|| PyTuple::is_type_of(object)
46+
|| object
47+
.is_instance(&Self::type_object(object.py()).into_any())
48+
.unwrap_or_else(|err| {
49+
err.write_unraisable(object.py(), Some(object));
50+
false
51+
})
52+
}
53+
}
54+
2955
impl PySequence {
3056
/// Register a pyclass as a subclass of `collections.abc.Sequence` (from the Python standard
3157
/// library). This is equivalent to `collections.abc.Sequence.register(T)` in Python.
3258
/// This registration is required for a pyclass to be castable from `PyAny` to `PySequence`.
3359
pub fn register<T: PyTypeInfo>(py: Python<'_>) -> PyResult<()> {
3460
let ty = T::type_object(py);
35-
get_sequence_abc(py)?.call_method1("register", (ty,))?;
61+
Self::type_object(py).call_method1("register", (ty,))?;
3662
Ok(())
3763
}
3864
}
@@ -372,36 +398,10 @@ where
372398
Ok(v)
373399
}
374400

375-
fn get_sequence_abc(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
376-
static SEQUENCE_ABC: PyOnceLock<Py<PyType>> = PyOnceLock::new();
377-
378-
SEQUENCE_ABC.import(py, "collections.abc", "Sequence")
379-
}
380-
381-
impl PyTypeCheck for PySequence {
382-
const NAME: &'static str = "Sequence";
383-
#[cfg(feature = "experimental-inspect")]
384-
const PYTHON_TYPE: &'static str = "collections.abc.Sequence";
385-
386-
#[inline]
387-
fn type_check(object: &Bound<'_, PyAny>) -> bool {
388-
// Using `is_instance` for `collections.abc.Sequence` is slow, so provide
389-
// optimized cases for list and tuples as common well-known sequences
390-
PyList::is_type_of(object)
391-
|| PyTuple::is_type_of(object)
392-
|| get_sequence_abc(object.py())
393-
.and_then(|abc| object.is_instance(abc))
394-
.unwrap_or_else(|err| {
395-
err.write_unraisable(object.py(), Some(object));
396-
false
397-
})
398-
}
399-
}
400-
401401
#[cfg(test)]
402402
mod tests {
403403
use crate::types::{PyAnyMethods, PyList, PySequence, PySequenceMethods, PyTuple};
404-
use crate::{ffi, IntoPyObject, Py, PyAny, Python};
404+
use crate::{ffi, IntoPyObject, Py, PyAny, PyTypeInfo, Python};
405405
use std::ptr;
406406

407407
fn get_object() -> Py<PyAny> {
@@ -827,4 +827,13 @@ mod tests {
827827
assert!(seq_from.to_list().is_ok());
828828
});
829829
}
830+
831+
#[test]
832+
fn test_type_object() {
833+
Python::attach(|py| {
834+
let abc = PySequence::type_object(py);
835+
assert!(PyList::empty(py).is_instance(&abc).unwrap());
836+
assert!(PyTuple::empty(py).is_instance(&abc).unwrap());
837+
})
838+
}
830839
}

0 commit comments

Comments
 (0)