Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added as_super and into_super methods for Bound<T: PyClass> #4351

Merged
merged 8 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions newsfragments/4351.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `as_super` and `into_super` methods for `Bound<T: PyClass>`.
142 changes: 142 additions & 0 deletions src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,110 @@ where
self.1.get()
}

/// Upcast this `Bound<PyClass>` to its base type by reference.
///
/// If this type defined an explicit base class in its`pyclass` declaration
JRRudy1 marked this conversation as resolved.
Show resolved Hide resolved
/// (e.g. `#[pyclass(extends = BaseType)]`), the returned type will be
/// `&Bound<BaseType>`. If an explicit base class was _not_ declared, the
/// return value will be `&Bound<PyAny>` (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<BaseType>`.
///
/// 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<String>;
/// }
/// impl<'py> MyClassMethods<'py> for Bound<'py, BaseClass> {
/// fn pyrepr(&self) -> PyResult<String> {
/// 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<PyClass>` to its base type by value.
///
/// If this type defined an explicit base class in its`pyclass` declaration
JRRudy1 marked this conversation as resolved.
Show resolved Hide resolved
/// (e.g. `#[pyclass(extends = BaseType)]`), the returned type will be
/// `Bound<BaseType>`. If an explicit base class was _not_ declared, the
/// return value will be `Bound<PyAny>` (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<BaseType>`.
///
/// See also the [`as_super`] method to upcast by value, and the
Icxolu marked this conversation as resolved.
Show resolved Hide resolved
/// [`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<String>;
/// }
/// impl<'py> MyClassMethods<'py> for Bound<'py, BaseClass> {
/// fn pyrepr(self) -> PyResult<String> {
/// 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::as_any
Icxolu marked this conversation as resolved.
Show resolved Hide resolved
/// [`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<T> {
self.1.get_class_object()
Expand Down Expand Up @@ -2324,5 +2428,43 @@ a = A()
}
})
}

#[crate::pyclass(crate = "crate", subclass)]
struct BaseClass;

trait MyClassMethods<'py>: Sized {
fn pyrepr_by_ref(&self) -> PyResult<String>;
fn pyrepr_by_val(self) -> PyResult<String> {
self.pyrepr_by_ref()
}
}
impl<'py> MyClassMethods<'py> for Bound<'py, BaseClass> {
fn pyrepr_by_ref(&self) -> PyResult<String> {
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());
})
}
}
}
Loading