Skip to content

Commit

Permalink
fixup docs and clippy
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Dec 9, 2023
1 parent d3b993d commit 382a436
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 59 deletions.
3 changes: 3 additions & 0 deletions src/conversions/num_bigint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ use crate::{
FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject,
};

#[cfg(Py_LIMITED_API)]
use crate::types::bytes::PyBytesMethods;

use num_bigint::{BigInt, BigUint};

#[cfg(not(Py_LIMITED_API))]
Expand Down
4 changes: 2 additions & 2 deletions src/conversions/num_complex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ macro_rules! complex_conversion {
unsafe {
use crate::types::any::PyAnyMethods;
let obj = if obj.is_instance_of::<PyComplex>() {
obj
obj.clone()
} else if let Some(method) =
obj.lookup_special(crate::intern!(obj.py(), "__complex__"))?
{
Expand All @@ -157,7 +157,7 @@ macro_rules! complex_conversion {
// `obj` might still implement `__float__` or `__index__`, which will be
// handled by `PyComplex_{Real,Imag}AsDouble`, including propagating any
// errors if those methods don't exist / raise exceptions.
obj
obj.clone()
};
let ptr = obj.as_ptr();
let real = ffi::PyComplex_RealAsDouble(ptr);
Expand Down
8 changes: 1 addition & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,13 +326,7 @@ pub mod backcompat {
/// Extracts `Self` from the source `PyObject`.
fn extract(ob: &'source PyAny) -> PyResult<Self>;

/// Extracts the type hint information for this type when it appears as an argument.
///
/// For example, `Vec<u32>` would return `Sequence[int]`.
/// The default implementation returns `Any`, which is correct for any type.
///
/// For most types, the return value for this method will be identical to that of [`IntoPy::type_output`].
/// It may be different for some types, such as `Dict`, to allow duck-typing: functions return `Dict` but take `Mapping` as argument.
/// See [`crate::FromPyObject::type_input`].
#[cfg(feature = "experimental-inspect")]
fn type_input() -> TypeInfo {
TypeInfo::Any
Expand Down
113 changes: 63 additions & 50 deletions src/types/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,51 +137,6 @@ impl PyAny {
.map(Py2::into_gil_ref)
}

/// Retrieve an attribute value, skipping the instance dictionary during the lookup but still
/// binding the object to the instance.
///
/// This is useful when trying to resolve Python's "magic" methods like `__getitem__`, which
/// are looked up starting from the type object. This returns an `Option` as it is not
/// typically a direct error for the special lookup to fail, as magic methods are optional in
/// many situations in which they might be called.
///
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `attr_name`.
#[allow(dead_code)] // Currently only used with num-complex+abi3, so dead without that.
pub(crate) fn lookup_special<N>(&self, attr_name: N) -> PyResult<Option<&PyAny>>
where
N: IntoPy<Py<PyString>>,
{
let py = self.py();
let self_type = self.get_type();
let attr = if let Ok(attr) = self_type.getattr(attr_name) {
attr
} else {
return Ok(None);
};

// Manually resolve descriptor protocol.
if cfg!(Py_3_10)
|| unsafe { ffi::PyType_HasFeature(attr.get_type_ptr(), ffi::Py_TPFLAGS_HEAPTYPE) } != 0
{
// This is the preferred faster path, but does not work on static types (generally,
// types defined in extension modules) before Python 3.10.
unsafe {
let descr_get_ptr = ffi::PyType_GetSlot(attr.get_type_ptr(), ffi::Py_tp_descr_get);
if descr_get_ptr.is_null() {
return Ok(Some(attr));
}
let descr_get: ffi::descrgetfunc = std::mem::transmute(descr_get_ptr);
let ret = descr_get(attr.as_ptr(), self.as_ptr(), self_type.as_ptr());
py.from_owned_ptr_or_err(ret).map(Some)
}
} else if let Ok(descr_get) = attr.get_type().getattr(crate::intern!(py, "__get__")) {
descr_get.call1((attr, self, self_type)).map(Some)
} else {
Ok(Some(attr))
}
}

/// Sets an attribute value.
///
/// This is equivalent to the Python expression `self.attr_name = value`.
Expand Down Expand Up @@ -2199,12 +2154,60 @@ impl<'py> PyAnyMethods<'py> for Py2<'py, PyAny> {
}
}

impl<'py> Py2<'py, PyAny> {
/// Retrieve an attribute value, skipping the instance dictionary during the lookup but still
/// binding the object to the instance.
///
/// This is useful when trying to resolve Python's "magic" methods like `__getitem__`, which
/// are looked up starting from the type object. This returns an `Option` as it is not
/// typically a direct error for the special lookup to fail, as magic methods are optional in
/// many situations in which they might be called.
///
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `attr_name`.
#[allow(dead_code)] // Currently only used with num-complex+abi3, so dead without that.
pub(crate) fn lookup_special<N>(&self, attr_name: N) -> PyResult<Option<Py2<'py, PyAny>>>
where
N: IntoPy<Py<PyString>>,
{
let py = self.py();
let self_type = self.get_type();
let attr = if let Ok(attr) = self_type.getattr(attr_name) {
attr
} else {
return Ok(None);
};

// Manually resolve descriptor protocol.
if cfg!(Py_3_10)
|| unsafe { ffi::PyType_HasFeature(attr.get_type_ptr(), ffi::Py_TPFLAGS_HEAPTYPE) } != 0
{
// This is the preferred faster path, but does not work on static types (generally,
// types defined in extension modules) before Python 3.10.
unsafe {
let descr_get_ptr = ffi::PyType_GetSlot(attr.get_type_ptr(), ffi::Py_tp_descr_get);
if descr_get_ptr.is_null() {
return Ok(Some(attr));
}
let descr_get: ffi::descrgetfunc = std::mem::transmute(descr_get_ptr);
let ret = descr_get(attr.as_ptr(), self.as_ptr(), self_type.as_ptr());
ret.assume_owned_or_ffi_error(py).map(Some)
}
} else if let Ok(descr_get) = attr.get_type().getattr(crate::intern!(py, "__get__")) {
descr_get.call1((attr, self, self_type)).map(Some)
} else {
Ok(Some(attr))
}
}
}

#[cfg(test)]
mod tests {
use super::PyAnyMethods;
use crate::{
basic::CompareOp,
types::{IntoPyDict, PyAny, PyBool, PyList, PyLong, PyModule},
PyTypeInfo, Python, ToPyObject,
Py2, PyTypeInfo, Python, ToPyObject,
};

#[test]
Expand Down Expand Up @@ -2249,8 +2252,13 @@ class NonHeapNonDescriptorInt:
.unwrap();

let int = crate::intern!(py, "__int__");
let eval_int =
|obj: &PyAny| obj.lookup_special(int)?.unwrap().call0()?.extract::<u32>();
let eval_int = |obj: &PyAny| {
Py2::borrowed_from_gil_ref(&obj)
.lookup_special(int)?
.unwrap()
.call0()?
.extract::<u32>()
};

let simple = module.getattr("SimpleInt").unwrap().call0().unwrap();
assert_eq!(eval_int(simple).unwrap(), 1);
Expand All @@ -2259,7 +2267,10 @@ class NonHeapNonDescriptorInt:
let no_descriptor = module.getattr("NoDescriptorInt").unwrap().call0().unwrap();
assert_eq!(eval_int(no_descriptor).unwrap(), 1);
let missing = module.getattr("NoInt").unwrap().call0().unwrap();
assert!(missing.lookup_special(int).unwrap().is_none());
assert!(Py2::borrowed_from_gil_ref(&missing)
.lookup_special(int)
.unwrap()
.is_none());
// Note the instance override should _not_ call the instance method that returns 2,
// because that's not how special lookups are meant to work.
let instance_override = module.getattr("instance_override").unwrap();
Expand All @@ -2269,7 +2280,9 @@ class NonHeapNonDescriptorInt:
.unwrap()
.call0()
.unwrap();
assert!(descriptor_error.lookup_special(int).is_err());
assert!(Py2::borrowed_from_gil_ref(&descriptor_error)
.lookup_special(int)
.is_err());
let nonheap_nondescriptor = module
.getattr("NonHeapNonDescriptorInt")
.unwrap()
Expand Down

0 comments on commit 382a436

Please sign in to comment.