From d598d1f4209cfce7be2208f6ca88b7aaceaf6600 Mon Sep 17 00:00:00 2001 From: Joshua Rudolph Date: Sun, 14 Jul 2024 17:11:32 -0500 Subject: [PATCH] Added `as_super` and `into_super` methods for `Bound` (#4351) * Added `Bound::as_super` and `Bound::into_super` methods. * Added tests for the `Bound::as_super` and `Bound::into_super` methods. * Added newsfragment entry. * Fixed newsfragment PR number. * Fixed doc links. * Fixed missing spaces in method docstrings. Co-authored-by: Lily Foote * fixup docs * fixup docs --------- Co-authored-by: jrudolph Co-authored-by: Lily Foote Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com> --- newsfragments/4351.added.md | 1 + src/instance.rs | 142 ++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 newsfragments/4351.added.md diff --git a/newsfragments/4351.added.md b/newsfragments/4351.added.md new file mode 100644 index 00000000000..f9276ae0d4f --- /dev/null +++ b/newsfragments/4351.added.md @@ -0,0 +1 @@ +Added `as_super` and `into_super` methods for `Bound`. \ No newline at end of file diff --git a/src/instance.rs b/src/instance.rs index 6967306a73f..3b8e2529a6e 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -359,6 +359,110 @@ where self.1.get() } + /// Upcast this `Bound` to its base type by reference. + /// + /// If this type defined an explicit base class in its `pyclass` declaration + /// (e.g. `#[pyclass(extends = BaseType)]`), the returned type will be + /// `&Bound`. If an explicit base class was _not_ declared, the + /// return value will be `&Bound` (making this method equivalent + /// to [`as_any`]). + /// + /// This method is particularly useful for calling methods defined in an + /// extension trait that has been implemented for `Bound`. + /// + /// See also the [`into_super`] method to upcast by value, and the + /// [`PyRef::as_super`]/[`PyRefMut::as_super`] methods for upcasting a pyclass + /// that has already been [`borrow`]ed. + /// + /// # Example: Calling a method defined on the `Bound` base type + /// + /// ```rust + /// # fn main() { + /// use pyo3::prelude::*; + /// + /// #[pyclass(subclass)] + /// struct BaseClass; + /// + /// trait MyClassMethods<'py> { + /// fn pyrepr(&self) -> PyResult; + /// } + /// impl<'py> MyClassMethods<'py> for Bound<'py, BaseClass> { + /// fn pyrepr(&self) -> PyResult { + /// self.call_method0("__repr__")?.extract() + /// } + /// } + /// + /// #[pyclass(extends = BaseClass)] + /// struct SubClass; + /// + /// Python::with_gil(|py| { + /// let obj = Bound::new(py, (SubClass, BaseClass)).unwrap(); + /// assert!(obj.as_super().pyrepr().is_ok()); + /// }) + /// # } + /// ``` + /// + /// [`as_any`]: Bound::as_any + /// [`into_super`]: Bound::into_super + /// [`borrow`]: Bound::borrow + #[inline] + pub fn as_super(&self) -> &Bound<'py, T::BaseType> { + // a pyclass can always be safely "downcast" to its base type + unsafe { self.as_any().downcast_unchecked() } + } + + /// Upcast this `Bound` to its base type by value. + /// + /// If this type defined an explicit base class in its `pyclass` declaration + /// (e.g. `#[pyclass(extends = BaseType)]`), the returned type will be + /// `Bound`. If an explicit base class was _not_ declared, the + /// return value will be `Bound` (making this method equivalent + /// to [`into_any`]). + /// + /// This method is particularly useful for calling methods defined in an + /// extension trait that has been implemented for `Bound`. + /// + /// See also the [`as_super`] method to upcast by reference, and the + /// [`PyRef::into_super`]/[`PyRefMut::into_super`] methods for upcasting a pyclass + /// that has already been [`borrow`]ed. + /// + /// # Example: Calling a method defined on the `Bound` base type + /// + /// ```rust + /// # fn main() { + /// use pyo3::prelude::*; + /// + /// #[pyclass(subclass)] + /// struct BaseClass; + /// + /// trait MyClassMethods<'py> { + /// fn pyrepr(self) -> PyResult; + /// } + /// impl<'py> MyClassMethods<'py> for Bound<'py, BaseClass> { + /// fn pyrepr(self) -> PyResult { + /// self.call_method0("__repr__")?.extract() + /// } + /// } + /// + /// #[pyclass(extends = BaseClass)] + /// struct SubClass; + /// + /// Python::with_gil(|py| { + /// let obj = Bound::new(py, (SubClass, BaseClass)).unwrap(); + /// assert!(obj.into_super().pyrepr().is_ok()); + /// }) + /// # } + /// ``` + /// + /// [`into_any`]: Bound::into_any + /// [`as_super`]: Bound::as_super + /// [`borrow`]: Bound::borrow + #[inline] + pub fn into_super(self) -> Bound<'py, T::BaseType> { + // a pyclass can always be safely "downcast" to its base type + unsafe { self.into_any().downcast_into_unchecked() } + } + #[inline] pub(crate) fn get_class_object(&self) -> &PyClassObject { self.1.get_class_object() @@ -2324,5 +2428,43 @@ a = A() } }) } + + #[crate::pyclass(crate = "crate", subclass)] + struct BaseClass; + + trait MyClassMethods<'py>: Sized { + fn pyrepr_by_ref(&self) -> PyResult; + fn pyrepr_by_val(self) -> PyResult { + self.pyrepr_by_ref() + } + } + impl<'py> MyClassMethods<'py> for Bound<'py, BaseClass> { + fn pyrepr_by_ref(&self) -> PyResult { + self.call_method0("__repr__")?.extract() + } + } + + #[crate::pyclass(crate = "crate", extends = BaseClass)] + struct SubClass; + + #[test] + fn test_as_super() { + Python::with_gil(|py| { + let obj = Bound::new(py, (SubClass, BaseClass)).unwrap(); + let _: &Bound<'_, BaseClass> = obj.as_super(); + let _: &Bound<'_, PyAny> = obj.as_super().as_super(); + assert!(obj.as_super().pyrepr_by_ref().is_ok()); + }) + } + + #[test] + fn test_into_super() { + Python::with_gil(|py| { + let obj = Bound::new(py, (SubClass, BaseClass)).unwrap(); + let _: Bound<'_, BaseClass> = obj.clone().into_super(); + let _: Bound<'_, PyAny> = obj.clone().into_super().into_super(); + assert!(obj.into_super().pyrepr_by_val().is_ok()); + }) + } } }