Skip to content

Commit

Permalink
Merge pull request #3763 from Icxolu/slice-traceback
Browse files Browse the repository at this point in the history
implement `PyTracebackMethods` and `PySliceMethods`
  • Loading branch information
davidhewitt committed Jan 27, 2024
2 parents f449fc0 + 7fddd98 commit f09ad1e
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 35 deletions.
2 changes: 1 addition & 1 deletion guide/src/python_from_rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ class House(object):
}
Err(e) => {
house
.call_method1("__exit__", (e.get_type(py), e.value(py), e.traceback(py)))
.call_method1("__exit__", (e.get_type(py), e.value(py), e.traceback_bound(py)))
.unwrap();
}
}
Expand Down
16 changes: 11 additions & 5 deletions src/err/err_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
exceptions::{PyBaseException, PyTypeError},
ffi,
types::{PyTraceback, PyType},
IntoPy, Py, PyAny, PyObject, PyTypeInfo, Python,
Bound, IntoPy, Py, PyAny, PyObject, PyTypeInfo, Python,
};

#[derive(Clone)]
Expand All @@ -26,15 +26,21 @@ impl PyErrStateNormalized {
}

#[cfg(not(Py_3_12))]
pub(crate) fn ptraceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> {
pub(crate) fn ptraceback<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyTraceback>> {
self.ptraceback
.as_ref()
.map(|traceback| traceback.as_ref(py))
.map(|traceback| traceback.bind(py).clone())
}

#[cfg(Py_3_12)]
pub(crate) fn ptraceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> {
unsafe { py.from_owned_ptr_or_opt(ffi::PyException_GetTraceback(self.pvalue.as_ptr())) }
pub(crate) fn ptraceback<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyTraceback>> {
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::types::any::PyAnyMethods;
unsafe {
ffi::PyException_GetTraceback(self.pvalue.as_ptr())
.assume_owned_or_opt(py)
.map(|b| b.downcast_into_unchecked())
}
}

#[cfg(Py_3_12)]
Expand Down
34 changes: 26 additions & 8 deletions src/err/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,18 @@ impl PyErr {
exc
}

/// Deprecated form of [`PyErr::traceback_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyErr::traceback` will be replaced by `PyErr::traceback_bound` in a future PyO3 version"
)
)]
pub fn traceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> {
self.normalized(py).ptraceback(py).map(|b| b.into_gil_ref())
}

/// Returns the traceback of this exception object.
///
/// # Examples
Expand All @@ -280,10 +292,10 @@ impl PyErr {
///
/// Python::with_gil(|py| {
/// let err = PyTypeError::new_err(("some type error",));
/// assert!(err.traceback(py).is_none());
/// assert!(err.traceback_bound(py).is_none());
/// });
/// ```
pub fn traceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> {
pub fn traceback_bound<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyTraceback>> {
self.normalized(py).ptraceback(py)
}

Expand Down Expand Up @@ -476,10 +488,16 @@ impl PyErr {

#[cfg(not(Py_3_12))]
unsafe {
// keep the bound `traceback` alive for entire duration of
// PyErr_Display. if we inline this, the `Bound` will be dropped
// after the argument got evaluated, leading to call with a dangling
// pointer.
let traceback = self.traceback_bound(py);
ffi::PyErr_Display(
self.get_type(py).as_ptr(),
self.value(py).as_ptr(),
self.traceback(py)
traceback
.as_ref()
.map_or(std::ptr::null_mut(), |traceback| traceback.as_ptr()),
)
}
Expand Down Expand Up @@ -658,15 +676,15 @@ impl PyErr {
///
/// # Examples
/// ```rust
/// use pyo3::{exceptions::PyTypeError, PyErr, Python};
/// use pyo3::{exceptions::PyTypeError, PyErr, Python, prelude::PyAnyMethods};
/// Python::with_gil(|py| {
/// let err: PyErr = PyTypeError::new_err(("some type error",));
/// let err_clone = err.clone_ref(py);
/// assert!(err.get_type(py).is(err_clone.get_type(py)));
/// assert!(err.value(py).is(err_clone.value(py)));
/// match err.traceback(py) {
/// None => assert!(err_clone.traceback(py).is_none()),
/// Some(tb) => assert!(err_clone.traceback(py).unwrap().is(tb)),
/// match err.traceback_bound(py) {
/// None => assert!(err_clone.traceback_bound(py).is_none()),
/// Some(tb) => assert!(err_clone.traceback_bound(py).unwrap().is(&tb)),
/// }
/// });
/// ```
Expand Down Expand Up @@ -747,7 +765,7 @@ impl std::fmt::Debug for PyErr {
f.debug_struct("PyErr")
.field("type", self.get_type(py))
.field("value", self.value(py))
.field("traceback", &self.traceback(py))
.field("traceback", &self.traceback_bound(py))
.finish()
})
}
Expand Down
3 changes: 2 additions & 1 deletion src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ macro_rules! import_exception {
impl $name {
fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject {
use $crate::sync::GILOnceCell;
use $crate::prelude::PyTracebackMethods;
static TYPE_OBJECT: GILOnceCell<$crate::Py<$crate::types::PyType>> =
GILOnceCell::new();

Expand All @@ -109,7 +110,7 @@ macro_rules! import_exception {
.import(stringify!($module))
.unwrap_or_else(|err| {
let traceback = err
.traceback(py)
.traceback_bound(py)
.map(|tb| tb.format().expect("raised exception will have a traceback"))
.unwrap_or_default();
::std::panic!("Can not import module {}: {}\n{}", stringify!($module), err, traceback);
Expand Down
1 change: 1 addition & 0 deletions src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ pub use crate::types::module::PyModuleMethods;
pub use crate::types::sequence::PySequenceMethods;
pub use crate::types::set::PySetMethods;
pub use crate::types::string::PyStringMethods;
pub use crate::types::traceback::PyTracebackMethods;
pub use crate::types::tuple::PyTupleMethods;
2 changes: 1 addition & 1 deletion src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,6 @@ pub(crate) mod sequence;
pub(crate) mod set;
mod slice;
pub(crate) mod string;
mod traceback;
pub(crate) mod traceback;
pub(crate) mod tuple;
mod typeobject;
68 changes: 59 additions & 9 deletions src/types/slice.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::err::{PyErr, PyResult};
use crate::ffi::{self, Py_ssize_t};
use crate::{PyAny, PyObject, Python, ToPyObject};
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::types::any::PyAnyMethods;
use crate::{Bound, PyAny, PyNativeType, PyObject, Python, ToPyObject};
use std::os::raw::c_long;

/// Represents a Python `slice`.
Expand Down Expand Up @@ -42,31 +44,79 @@ impl PySliceIndices {
}

impl PySlice {
/// Constructs a new slice with the given elements.
/// Deprecated form of `PySlice::new_bound`.
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PySlice::new` will be replaced by `PySlice::new_bound` in a future PyO3 version"
)
)]
pub fn new(py: Python<'_>, start: isize, stop: isize, step: isize) -> &PySlice {
Self::new_bound(py, start, stop, step).into_gil_ref()
}

/// Constructs a new slice with the given elements.
pub fn new_bound(py: Python<'_>, start: isize, stop: isize, step: isize) -> Bound<'_, PySlice> {
unsafe {
let ptr = ffi::PySlice_New(
ffi::PySlice_New(
ffi::PyLong_FromSsize_t(start),
ffi::PyLong_FromSsize_t(stop),
ffi::PyLong_FromSsize_t(step),
);
py.from_owned_ptr(ptr)
)
.assume_owned(py)
.downcast_into_unchecked()
}
}

/// Constructs a new full slice that is equivalent to `::`.
/// Deprecated form of `PySlice::full_bound`.
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PySlice::full` will be replaced by `PySlice::full_bound` in a future PyO3 version"
)
)]
pub fn full(py: Python<'_>) -> &PySlice {
unsafe {
let ptr = ffi::PySlice_New(ffi::Py_None(), ffi::Py_None(), ffi::Py_None());
py.from_owned_ptr(ptr)
}
}

/// Constructs a new full slice that is equivalent to `::`.
pub fn full_bound(py: Python<'_>) -> Bound<'_, PySlice> {
unsafe {
ffi::PySlice_New(ffi::Py_None(), ffi::Py_None(), ffi::Py_None())
.assume_owned(py)
.downcast_into_unchecked()
}
}

/// Retrieves the start, stop, and step indices from the slice object,
/// assuming a sequence of length `length`, and stores the length of the
/// slice in its `slicelength` member.
#[inline]
pub fn indices(&self, length: c_long) -> PyResult<PySliceIndices> {
self.as_borrowed().indices(length)
}
}

/// Implementation of functionality for [`PySlice`].
///
/// These methods are defined for the `Bound<'py, PyTuple>` smart pointer, so to use method call
/// syntax these methods are separated into a trait, because stable Rust does not yet support
/// `arbitrary_self_types`.
#[doc(alias = "PySlice")]
pub trait PySliceMethods<'py> {
/// Retrieves the start, stop, and step indices from the slice object,
/// assuming a sequence of length `length`, and stores the length of the
/// slice in its `slicelength` member.
fn indices(&self, length: c_long) -> PyResult<PySliceIndices>;
}

impl<'py> PySliceMethods<'py> for Bound<'py, PySlice> {
fn indices(&self, length: c_long) -> PyResult<PySliceIndices> {
// non-negative Py_ssize_t should always fit into Rust usize
unsafe {
let mut slicelength: isize = 0;
Expand Down Expand Up @@ -97,7 +147,7 @@ impl PySlice {

impl ToPyObject for PySliceIndices {
fn to_object(&self, py: Python<'_>) -> PyObject {
PySlice::new(py, self.start, self.stop, self.step).into()
PySlice::new_bound(py, self.start, self.stop, self.step).into()
}
}

Expand All @@ -108,7 +158,7 @@ mod tests {
#[test]
fn test_py_slice_new() {
Python::with_gil(|py| {
let slice = PySlice::new(py, isize::MIN, isize::MAX, 1);
let slice = PySlice::new_bound(py, isize::MIN, isize::MAX, 1);
assert_eq!(
slice.getattr("start").unwrap().extract::<isize>().unwrap(),
isize::MIN
Expand All @@ -127,7 +177,7 @@ mod tests {
#[test]
fn test_py_slice_full() {
Python::with_gil(|py| {
let slice = PySlice::full(py);
let slice = PySlice::full_bound(py);
assert!(slice.getattr("start").unwrap().is_none(),);
assert!(slice.getattr("stop").unwrap().is_none(),);
assert!(slice.getattr("step").unwrap().is_none(),);
Expand Down
68 changes: 59 additions & 9 deletions src/types/traceback.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::err::{error_on_minusone, PyResult};
use crate::ffi;
use crate::types::PyString;
use crate::PyAny;
use crate::{ffi, Bound};
use crate::{PyAny, PyNativeType};

/// Represents a Python traceback.
#[repr(transparent)]
Expand All @@ -24,14 +24,14 @@ impl PyTraceback {
/// The following code formats a Python traceback and exception pair from Rust:
///
/// ```rust
/// # use pyo3::{Python, PyResult};
/// # use pyo3::{Python, PyResult, prelude::PyTracebackMethods};
/// # let result: PyResult<()> =
/// Python::with_gil(|py| {
/// let err = py
/// .run("raise Exception('banana')", None, None)
/// .expect_err("raise will create a Python error");
///
/// let traceback = err.traceback(py).expect("raised exception will have a traceback");
/// let traceback = err.traceback_bound(py).expect("raised exception will have a traceback");
/// assert_eq!(
/// format!("{}{}", traceback.format()?, err),
/// "\
Expand All @@ -46,6 +46,53 @@ impl PyTraceback {
/// # result.expect("example failed");
/// ```
pub fn format(&self) -> PyResult<String> {
self.as_borrowed().format()
}
}

/// Implementation of functionality for [`PyTraceback`].
///
/// These methods are defined for the `Bound<'py, PyTraceback>` smart pointer, so to use method call
/// syntax these methods are separated into a trait, because stable Rust does not yet support
/// `arbitrary_self_types`.
#[doc(alias = "PyTraceback")]
pub trait PyTracebackMethods<'py> {
/// Formats the traceback as a string.
///
/// This does not include the exception type and value. The exception type and value can be
/// formatted using the `Display` implementation for `PyErr`.
///
/// # Example
///
/// The following code formats a Python traceback and exception pair from Rust:
///
/// ```rust
/// # use pyo3::{Python, PyResult, prelude::PyTracebackMethods};
/// # let result: PyResult<()> =
/// Python::with_gil(|py| {
/// let err = py
/// .run("raise Exception('banana')", None, None)
/// .expect_err("raise will create a Python error");
///
/// let traceback = err.traceback_bound(py).expect("raised exception will have a traceback");
/// assert_eq!(
/// format!("{}{}", traceback.format()?, err),
/// "\
/// Traceback (most recent call last):
/// File \"<string>\", line 1, in <module>
/// Exception: banana\
/// "
/// );
/// Ok(())
/// })
/// # ;
/// # result.expect("example failed");
/// ```
fn format(&self) -> PyResult<String>;
}

impl<'py> PyTracebackMethods<'py> for Bound<'py, PyTraceback> {
fn format(&self) -> PyResult<String> {
let py = self.py();
let string_io = py
.import(intern!(py, "io"))?
Expand All @@ -65,7 +112,10 @@ impl PyTraceback {

#[cfg(test)]
mod tests {
use crate::{prelude::*, types::PyDict};
use crate::{
prelude::*,
types::{traceback::PyTracebackMethods, PyDict},
};

#[test]
fn format_traceback() {
Expand All @@ -75,7 +125,7 @@ mod tests {
.expect_err("raising should have given us an error");

assert_eq!(
err.traceback(py).unwrap().format().unwrap(),
err.traceback_bound(py).unwrap().format().unwrap(),
"Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\n"
);
})
Expand All @@ -99,7 +149,7 @@ except Exception as e:
.unwrap();
let err = PyErr::from_value(locals.get_item("err").unwrap().unwrap());
let traceback = err.value(py).getattr("__traceback__").unwrap();
assert!(err.traceback(py).unwrap().is(traceback));
assert!(err.traceback_bound(py).unwrap().is(traceback));
})
}

Expand All @@ -119,10 +169,10 @@ def f():
.unwrap();
let f = locals.get_item("f").unwrap().unwrap();
let err = f.call0().unwrap_err();
let traceback = err.traceback(py).unwrap();
let traceback = err.traceback_bound(py).unwrap();
let err_object = err.clone_ref(py).into_py(py).into_ref(py);

assert!(err_object.getattr("__traceback__").unwrap().is(traceback));
assert!(err_object.getattr("__traceback__").unwrap().is(&traceback));
})
}
}
Loading

0 comments on commit f09ad1e

Please sign in to comment.