use libc::c_char;
use std::ffi::CString;
use std::ptr;
use crate::conversion::ToPyObject;
use crate::ffi;
#[cfg(feature = "python27-sys")]
use crate::objects::oldstyle::PyClass;
use crate::objects::{exc, PyObject, PyType};
use crate::python::{
PyClone, PyDrop, Python, PythonObject, PythonObjectDowncastError, PythonObjectWithTypeObject,
ToPythonPointer,
};
#[macro_export]
macro_rules! py_exception {
($module: ident, $name: ident, $base: ty) => {
pub struct $name($crate::PyObject);
$crate::pyobject_newtype!($name);
impl $name {
pub fn new<'p, T: $crate::ToPyObject>(
py: $crate::Python<'p>,
args: T,
) -> $crate::PyErr {
$crate::PyErr::new::<$name, T>(py, args)
}
}
impl $crate::PythonObjectWithCheckedDowncast for $name {
#[inline]
fn downcast_from<'p>(
py: $crate::Python<'p>,
obj: $crate::PyObject,
) -> Result<$name, $crate::PythonObjectDowncastError<'p>> {
if <$name as $crate::PythonObjectWithTypeObject>::type_object(py)
.is_instance(py, &obj)
{
Ok(unsafe { $crate::PythonObject::unchecked_downcast_from(obj) })
} else {
Err($crate::PythonObjectDowncastError::new(
py,
stringify!($name),
<$name as $crate::PythonObjectWithTypeObject>::type_object(py),
))
}
}
#[inline]
fn downcast_borrow_from<'a, 'p>(
py: $crate::Python<'p>,
obj: &'a $crate::PyObject,
) -> Result<&'a $name, $crate::PythonObjectDowncastError<'p>> {
if <$name as $crate::PythonObjectWithTypeObject>::type_object(py)
.is_instance(py, obj)
{
Ok(unsafe { $crate::PythonObject::unchecked_downcast_borrow_from(obj) })
} else {
Err($crate::PythonObjectDowncastError::new(
py,
stringify!($name),
<$name as $crate::PythonObjectWithTypeObject>::type_object(py),
))
}
}
}
impl $crate::PythonObjectWithTypeObject for $name {
#[inline]
fn type_object(py: $crate::Python) -> $crate::PyType {
unsafe {
static mut type_object: *mut $crate::_detail::ffi::PyTypeObject =
0 as *mut $crate::_detail::ffi::PyTypeObject;
if type_object.is_null() {
type_object = $crate::PyErr::new_type(
py,
concat!(stringify!($module), ".", stringify!($name)),
Some($crate::PythonObject::into_object(py.get_type::<$base>())),
None,
)
.as_type_ptr();
}
$crate::PyType::from_type_ptr(py, type_object)
}
}
}
};
($module: ident, $name: ident) => {
$crate::py_exception!($module, $name, $crate::exc::Exception);
};
}
#[derive(Debug)]
pub struct PyErr {
pub ptype: PyObject,
pub pvalue: Option<PyObject>,
pub ptraceback: Option<PyObject>,
}
pub type PyResult<T> = Result<T, PyErr>;
impl PyErr {
pub fn new<T, V>(py: Python, value: V) -> PyErr
where
T: PythonObjectWithTypeObject,
V: ToPyObject,
{
PyErr::new_helper(py, py.get_type::<T>(), value.to_py_object(py).into_object())
}
#[inline]
pub fn occurred(_: Python) -> bool {
unsafe { !ffi::PyErr_Occurred().is_null() }
}
pub fn new_type(
py: Python,
name: &str,
base: Option<PyObject>,
dict: Option<PyObject>,
) -> PyType {
let base: *mut ffi::PyObject = match base {
None => ptr::null_mut(),
Some(obj) => obj.steal_ptr(),
};
let dict: *mut ffi::PyObject = match dict {
None => ptr::null_mut(),
Some(obj) => obj.steal_ptr(),
};
unsafe {
let null_terminated_name = CString::new(name).unwrap();
let ptr: *mut ffi::PyObject =
ffi::PyErr_NewException(null_terminated_name.as_ptr() as *mut c_char, base, dict);
PyObject::from_borrowed_ptr(py, ptr).unchecked_cast_into::<PyType>()
}
}
pub fn fetch(py: Python) -> PyErr {
let mut ptype: *mut ffi::PyObject = ptr::null_mut();
let mut pvalue: *mut ffi::PyObject = ptr::null_mut();
let mut ptraceback: *mut ffi::PyObject = ptr::null_mut();
unsafe {
ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback);
PyErr::new_from_ffi_tuple(py, ptype, pvalue, ptraceback)
}
}
unsafe fn new_from_ffi_tuple(
py: Python,
ptype: *mut ffi::PyObject,
pvalue: *mut ffi::PyObject,
ptraceback: *mut ffi::PyObject,
) -> PyErr {
PyErr {
ptype: if ptype.is_null() {
py.get_type::<exc::SystemError>().into_object()
} else {
PyObject::from_owned_ptr(py, ptype)
},
pvalue: PyObject::from_owned_ptr_opt(py, pvalue),
ptraceback: PyObject::from_owned_ptr_opt(py, ptraceback),
}
}
fn new_helper(_py: Python, ty: PyType, value: PyObject) -> PyErr {
assert!(unsafe { ffi::PyExceptionClass_Check(ty.as_object().as_ptr()) } != 0);
PyErr {
ptype: ty.into_object(),
pvalue: Some(value),
ptraceback: None,
}
}
pub fn from_instance<O>(py: Python, obj: O) -> PyErr
where
O: PythonObject,
{
PyErr::from_instance_helper(py, obj.into_object())
}
fn from_instance_helper(py: Python, obj: PyObject) -> PyErr {
if unsafe { ffi::PyExceptionInstance_Check(obj.as_ptr()) } != 0 {
PyErr {
ptype: unsafe {
PyObject::from_borrowed_ptr(py, ffi::PyExceptionInstance_Class(obj.as_ptr()))
},
pvalue: Some(obj),
ptraceback: None,
}
} else if unsafe { ffi::PyExceptionClass_Check(obj.as_ptr()) } != 0 {
PyErr {
ptype: obj,
pvalue: None,
ptraceback: None,
}
} else {
PyErr {
ptype: py.get_type::<exc::TypeError>().into_object(),
pvalue: Some(
"exceptions must derive from BaseException"
.to_py_object(py)
.into_object(),
),
ptraceback: None,
}
}
}
#[inline]
pub fn new_lazy_init(exc: PyType, value: Option<PyObject>) -> PyErr {
PyErr {
ptype: exc.into_object(),
pvalue: value,
ptraceback: None,
}
}
pub fn print(self, py: Python) {
self.restore(py);
unsafe { ffi::PyErr_PrintEx(0) }
}
pub fn print_and_set_sys_last_vars(self, py: Python) {
self.restore(py);
unsafe { ffi::PyErr_PrintEx(1) }
}
pub fn matches<T>(&self, py: Python, exc: T) -> bool
where
T: ToPyObject,
{
exc.with_borrowed_ptr(py, |exc| unsafe {
ffi::PyErr_GivenExceptionMatches(self.ptype.as_ptr(), exc) != 0
})
}
pub fn normalize(&mut self, py: Python) {
unsafe {
std::ptr::write(self, std::ptr::read(self).into_normalized(py));
}
}
fn into_normalized(self, py: Python) -> PyErr {
let PyErr {
ptype,
pvalue,
ptraceback,
} = self;
let mut ptype = ptype.steal_ptr();
let mut pvalue = pvalue.steal_ptr(py);
let mut ptraceback = ptraceback.steal_ptr(py);
unsafe {
ffi::PyErr_NormalizeException(&mut ptype, &mut pvalue, &mut ptraceback);
PyErr::new_from_ffi_tuple(py, ptype, pvalue, ptraceback)
}
}
#[cfg(feature = "python27-sys")]
pub fn get_type(&self, py: Python) -> PyType {
match self.ptype.cast_as::<PyType>(py) {
Ok(t) => t.clone_ref(py),
Err(_) => match self.ptype.cast_as::<PyClass>(py) {
Ok(_) => py.get_type::<PyClass>(),
Err(_) => py.None().get_type(py),
},
}
}
#[cfg(not(feature = "python27-sys"))]
pub fn get_type(&self, py: Python) -> PyType {
match self.ptype.cast_as::<PyType>(py) {
Ok(t) => t.clone_ref(py),
Err(_) => py.None().get_type(py),
}
}
pub fn instance(&mut self, py: Python) -> PyObject {
self.normalize(py);
match self.pvalue {
Some(ref instance) => instance.clone_ref(py),
None => py.None(),
}
}
#[inline]
pub fn restore(self, py: Python) {
let PyErr {
ptype,
pvalue,
ptraceback,
} = self;
unsafe {
ffi::PyErr_Restore(
ptype.steal_ptr(),
pvalue.steal_ptr(py),
ptraceback.steal_ptr(py),
)
}
}
pub fn warn(py: Python, category: &PyObject, message: &str, stacklevel: i32) -> PyResult<()> {
let message = CString::new(message).unwrap();
unsafe {
error_on_minusone(
py,
ffi::PyErr_WarnEx(
category.as_ptr(),
message.as_ptr(),
stacklevel as ffi::Py_ssize_t,
),
)
}
}
}
impl PyDrop for PyErr {
fn release_ref(self, py: Python) {
self.ptype.release_ref(py);
self.pvalue.release_ref(py);
self.ptraceback.release_ref(py);
}
}
impl PyClone for PyErr {
fn clone_ref(&self, py: Python) -> PyErr {
PyErr {
ptype: self.ptype.clone_ref(py),
pvalue: self.pvalue.clone_ref(py),
ptraceback: self.ptraceback.clone_ref(py),
}
}
}
impl<'p> std::convert::From<PythonObjectDowncastError<'p>> for PyErr {
fn from(err: PythonObjectDowncastError<'p>) -> PyErr {
let msg = format!(
"Expected type that converts to {} but received {}",
err.expected_type_name,
err.received_type.name(err.py),
)
.to_py_object(err.py)
.into_object();
PyErr::new_lazy_init(err.py.get_type::<exc::TypeError>(), Some(msg))
}
}
#[inline]
pub unsafe fn result_from_owned_ptr(py: Python, p: *mut ffi::PyObject) -> PyResult<PyObject> {
if p.is_null() {
Err(PyErr::fetch(py))
} else {
Ok(PyObject::from_owned_ptr(py, p))
}
}
fn panic_after_error(_py: Python) -> ! {
unsafe {
ffi::PyErr_Print();
}
panic!("Python API called failed");
}
#[inline]
pub unsafe fn from_owned_ptr_or_panic(py: Python, p: *mut ffi::PyObject) -> PyObject {
if p.is_null() {
panic_after_error(py);
} else {
PyObject::from_owned_ptr(py, p)
}
}
pub unsafe fn result_cast_from_owned_ptr<T>(py: Python, p: *mut ffi::PyObject) -> PyResult<T>
where
T: crate::python::PythonObjectWithCheckedDowncast,
{
if p.is_null() {
Err(PyErr::fetch(py))
} else {
Ok(PyObject::from_owned_ptr(py, p).cast_into(py)?)
}
}
pub unsafe fn cast_from_owned_ptr_or_panic<T>(py: Python, p: *mut ffi::PyObject) -> T
where
T: crate::python::PythonObjectWithCheckedDowncast,
{
if p.is_null() {
panic_after_error(py);
} else {
PyObject::from_owned_ptr(py, p).cast_into(py).unwrap()
}
}
#[inline]
pub fn error_on_minusone(py: Python, result: libc::c_int) -> PyResult<()> {
if result != -1 {
Ok(())
} else {
Err(PyErr::fetch(py))
}
}
#[cfg(test)]
mod tests {
use crate::objects::exc;
use crate::{PyErr, Python};
#[test]
fn set_typeerror() {
let gil = Python::acquire_gil();
let py = gil.python();
PyErr::new_lazy_init(py.get_type::<exc::TypeError>(), None).restore(py);
assert!(PyErr::occurred(py));
drop(PyErr::fetch(py));
}
}