use crate::builtins::pystr::{PyStr, PyStrRef}; use crate::builtins::pytype::{PyType, PyTypeRef}; use crate::builtins::singletons::{PyNone, PyNoneRef}; use crate::builtins::traceback::PyTracebackRef; use crate::builtins::tuple::{PyTuple, PyTupleRef}; use crate::common::lock::PyRwLock; use crate::function::FuncArgs; use crate::py_io::{self, Write}; use crate::pyobject::StaticType; use crate::pyobject::{ BorrowValue, IntoPyObject, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::types::create_type_with_slots; use crate::VirtualMachine; use crate::{py_serde, sysmodule}; use crossbeam_utils::atomic::AtomicCell; use itertools::Itertools; use std::fmt; use std::fs::File; use std::io::{self, BufRead, BufReader}; #[pyclass(module = false, name = "BaseException")] pub struct PyBaseException { traceback: PyRwLock>, cause: PyRwLock>, context: PyRwLock>, suppress_context: AtomicCell, args: PyRwLock, } impl fmt::Debug for PyBaseException { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // TODO: implement more detailed, non-recursive Debug formatter f.write_str("PyBaseException") } } pub type PyBaseExceptionRef = PyRef; pub trait IntoPyException { fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef; } impl PyValue for PyBaseException { fn class(vm: &VirtualMachine) -> &PyTypeRef { &vm.ctx.exceptions.base_exception_type } } #[pyimpl(flags(BASETYPE, HAS_DICT))] impl PyBaseException { pub(crate) fn new(args: Vec, vm: &VirtualMachine) -> PyBaseException { PyBaseException { traceback: PyRwLock::new(None), cause: PyRwLock::new(None), context: PyRwLock::new(None), suppress_context: AtomicCell::new(false), args: PyRwLock::new(PyTupleRef::with_elements(args, &vm.ctx)), } } #[pyslot] fn tp_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult> { PyBaseException::new(args.args, vm).into_ref_with_type(vm, cls) } #[pymethod(name = "__init__")] fn init(&self, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { *self.args.write() = PyTupleRef::with_elements(args.args, &vm.ctx); Ok(()) } pub fn get_arg(&self, idx: usize) -> Option { self.args.read().borrow_value().get(idx).cloned() } #[pyproperty] pub fn args(&self) -> PyTupleRef { self.args.read().clone() } #[pyproperty(setter)] fn set_args(&self, args: PyIterable, vm: &VirtualMachine) -> PyResult<()> { let args = args.iter(vm)?.collect::>>()?; *self.args.write() = PyTupleRef::with_elements(args, &vm.ctx); Ok(()) } #[pyproperty(name = "__traceback__")] pub fn traceback(&self) -> Option { self.traceback.read().clone() } #[pyproperty(name = "__traceback__", setter)] pub fn set_traceback(&self, traceback: Option) { *self.traceback.write() = traceback; } #[pyproperty(name = "__cause__")] pub fn cause(&self) -> Option { self.cause.read().clone() } #[pyproperty(name = "__cause__", setter)] pub fn set_cause(&self, cause: Option) { *self.cause.write() = cause; } #[pyproperty(name = "__context__")] pub fn context(&self) -> Option { self.context.read().clone() } #[pyproperty(name = "__context__", setter)] pub fn set_context(&self, context: Option) { *self.context.write() = context; } #[pyproperty(name = "__suppress_context__")] fn get_suppress_context(&self) -> bool { self.suppress_context.load() } #[pyproperty(name = "__suppress_context__", setter)] fn set_suppress_context(&self, suppress_context: bool) { self.suppress_context.store(suppress_context); } #[pymethod] fn with_traceback(zelf: PyRef, tb: Option) -> PyResult { *zelf.traceback.write() = tb; Ok(zelf.as_object().clone()) } #[pymethod(name = "__str__")] fn str(&self, vm: &VirtualMachine) -> PyStrRef { let str_args = exception_args_as_string(vm, self.args(), true); match str_args.into_iter().exactly_one() { Err(i) if i.len() == 0 => PyStr::from("").into_ref(vm), Ok(s) => s, Err(i) => PyStr::from(format!("({})", i.format(", "))).into_ref(vm), } } #[pymethod(name = "__repr__")] fn repr(zelf: PyRef, vm: &VirtualMachine) -> String { let repr_args = exception_args_as_string(vm, zelf.args(), false); let cls = zelf.class(); format!("{}({})", cls.name, repr_args.iter().format(", ")) } } pub fn chain(e1: PyResult<()>, e2: PyResult) -> PyResult { match (e1, e2) { (Err(e1), Err(e)) => { e.set_context(Some(e1)); Err(e) } (Err(e), Ok(_)) | (Ok(()), Err(e)) => Err(e), (Ok(()), Ok(close_res)) => Ok(close_res), } } /// Print exception chain by calling sys.excepthook pub fn print_exception(vm: &VirtualMachine, exc: PyBaseExceptionRef) { let write_fallback = |exc, errstr| { if let Ok(stderr) = sysmodule::get_stderr(vm) { let mut stderr = py_io::PyWriter(stderr, vm); // if this fails stderr might be closed -- ignore it let _ = writeln!(stderr, "{}", errstr); let _ = write_exception(&mut stderr, vm, exc); } else { eprintln!("{}\nlost sys.stderr", errstr); let _ = write_exception(&mut io::stderr(), vm, exc); } }; if let Ok(excepthook) = vm.get_attribute(vm.sys_module.clone(), "excepthook") { let (exc_type, exc_val, exc_tb) = split(exc.clone(), vm); if let Err(eh_exc) = vm.invoke(&excepthook, vec![exc_type, exc_val, exc_tb]) { write_fallback(&eh_exc, "Error in sys.excepthook:"); write_fallback(&exc, "Original exception was:"); } } else { write_fallback(&exc, "missing sys.excepthook"); } } pub fn write_exception( output: &mut W, vm: &VirtualMachine, exc: &PyBaseExceptionRef, ) -> Result<(), W::Error> { if let Some(cause) = exc.cause() { write_exception(output, vm, &cause)?; writeln!( output, "\nThe above exception was the direct cause of the following exception:\n" )?; } else if let Some(context) = exc.context() { write_exception(output, vm, &context)?; writeln!( output, "\nDuring handling of the above exception, another exception occurred:\n" )?; } write_exception_inner(output, vm, exc) } fn print_source_line( output: &mut W, filename: &str, lineno: usize, ) -> Result<(), W::Error> { // TODO: use io.open() method instead, when available, according to https://github.com/python/cpython/blob/master/Python/traceback.c#L393 // TODO: support different encodings let file = match File::open(filename) { Ok(file) => file, Err(_) => return Ok(()), }; let file = BufReader::new(file); for (i, line) in file.lines().enumerate() { if i + 1 == lineno { if let Ok(line) = line { // Indented with 4 spaces writeln!(output, " {}", line.trim_start())?; } return Ok(()); } } Ok(()) } /// Print exception occurrence location from traceback element fn write_traceback_entry( output: &mut W, tb_entry: &PyTracebackRef, ) -> Result<(), W::Error> { let filename = tb_entry.frame.code.source_path.to_owned(); writeln!( output, r##" File "{}", line {}, in {}"##, filename, tb_entry.lineno, tb_entry.frame.code.obj_name )?; print_source_line(output, &filename, tb_entry.lineno)?; Ok(()) } /// Print exception with traceback pub fn write_exception_inner( output: &mut W, vm: &VirtualMachine, exc: &PyBaseExceptionRef, ) -> Result<(), W::Error> { if let Some(tb) = exc.traceback.read().clone() { writeln!(output, "Traceback (most recent call last):")?; for tb in tb.iter() { write_traceback_entry(output, &tb)?; } } let varargs = exc.args(); let args_repr = exception_args_as_string(vm, varargs, true); let exc_name = exc.class().name.clone(); match args_repr.len() { 0 => writeln!(output, "{}", exc_name), 1 => writeln!(output, "{}: {}", exc_name, args_repr[0]), _ => writeln!( output, "{}: ({})", exc_name, args_repr.into_iter().format(", ") ), } } fn exception_args_as_string( vm: &VirtualMachine, varargs: PyTupleRef, str_single: bool, ) -> Vec { let varargs = varargs.borrow_value(); match varargs.len() { 0 => vec![], 1 => { let args0_repr = if str_single { vm.to_str(&varargs[0]) .unwrap_or_else(|_| PyStr::from("").into_ref(vm)) } else { vm.to_repr(&varargs[0]) .unwrap_or_else(|_| PyStr::from("").into_ref(vm)) }; vec![args0_repr] } _ => varargs .iter() .map(|vararg| { vm.to_repr(vararg) .unwrap_or_else(|_| PyStr::from("").into_ref(vm)) }) .collect(), } } #[derive(Clone)] pub enum ExceptionCtor { Class(PyTypeRef), Instance(PyBaseExceptionRef), } impl TryFromObject for ExceptionCtor { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { obj.downcast::() .and_then(|cls| { if cls.issubclass(&vm.ctx.exceptions.base_exception_type) { Ok(Self::Class(cls)) } else { Err(cls.into_object()) } }) .or_else(|obj| obj.downcast::().map(Self::Instance)) .map_err(|obj| { vm.new_type_error(format!( "exceptions must be classes or instances deriving from BaseException, not {}", obj.class().name )) }) } } pub fn invoke( cls: PyTypeRef, args: Vec, vm: &VirtualMachine, ) -> PyResult { // TODO: fast-path built-in exceptions by directly instantiating them? Is that really worth it? let res = vm.invoke(cls.as_object(), args)?; PyBaseExceptionRef::try_from_object(vm, res) } impl ExceptionCtor { pub fn instantiate(self, vm: &VirtualMachine) -> PyResult { match self { Self::Class(cls) => invoke(cls, vec![], vm), Self::Instance(exc) => Ok(exc), } } pub fn instantiate_value( self, value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { let exc_inst = value.clone().downcast::().ok(); match (self, exc_inst) { // both are instances; which would we choose? (Self::Instance(_exc_a), Some(_exc_b)) => { Err(vm .new_type_error("instance exception may not have a separate value".to_owned())) } // if the "type" is an instance and the value isn't, use the "type" (Self::Instance(exc), None) => Ok(exc), // if the value is an instance of the type, use the instance value (Self::Class(cls), Some(exc)) if exc.isinstance(&cls) => Ok(exc), // otherwise; construct an exception of the type using the value as args (Self::Class(cls), _) => { let args = match_class!(match value { PyNone => vec![], tup @ PyTuple => tup.borrow_value().to_vec(), exc @ PyBaseException => exc.args().borrow_value().to_vec(), obj => vec![obj], }); invoke(cls, args, vm) } } } } pub fn split( exc: PyBaseExceptionRef, vm: &VirtualMachine, ) -> (PyObjectRef, PyObjectRef, PyObjectRef) { let tb = exc.traceback().into_pyobject(vm); (exc.clone_class().into_object(), exc.into_object(), tb) } /// Similar to PyErr_NormalizeException in CPython pub fn normalize( exc_type: PyObjectRef, exc_val: PyObjectRef, exc_tb: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { let ctor = ExceptionCtor::try_from_object(vm, exc_type)?; let exc = ctor.instantiate_value(exc_val, vm)?; if let Some(tb) = Option::::try_from_object(vm, exc_tb)? { exc.set_traceback(Some(tb)); } Ok(exc) } #[derive(Debug, Clone)] pub struct ExceptionZoo { pub base_exception_type: PyTypeRef, pub system_exit: PyTypeRef, pub keyboard_interrupt: PyTypeRef, pub generator_exit: PyTypeRef, pub exception_type: PyTypeRef, pub stop_iteration: PyTypeRef, pub stop_async_iteration: PyTypeRef, pub arithmetic_error: PyTypeRef, pub floating_point_error: PyTypeRef, pub overflow_error: PyTypeRef, pub zero_division_error: PyTypeRef, pub assertion_error: PyTypeRef, pub attribute_error: PyTypeRef, pub buffer_error: PyTypeRef, pub eof_error: PyTypeRef, pub import_error: PyTypeRef, pub module_not_found_error: PyTypeRef, pub lookup_error: PyTypeRef, pub index_error: PyTypeRef, pub key_error: PyTypeRef, pub memory_error: PyTypeRef, pub name_error: PyTypeRef, pub unbound_local_error: PyTypeRef, pub os_error: PyTypeRef, pub blocking_io_error: PyTypeRef, pub child_process_error: PyTypeRef, pub connection_error: PyTypeRef, pub broken_pipe_error: PyTypeRef, pub connection_aborted_error: PyTypeRef, pub connection_refused_error: PyTypeRef, pub connection_reset_error: PyTypeRef, pub file_exists_error: PyTypeRef, pub file_not_found_error: PyTypeRef, pub interrupted_error: PyTypeRef, pub is_a_directory_error: PyTypeRef, pub not_a_directory_error: PyTypeRef, pub permission_error: PyTypeRef, pub process_lookup_error: PyTypeRef, pub timeout_error: PyTypeRef, pub reference_error: PyTypeRef, pub runtime_error: PyTypeRef, pub not_implemented_error: PyTypeRef, pub recursion_error: PyTypeRef, pub syntax_error: PyTypeRef, pub target_scope_error: PyTypeRef, pub indentation_error: PyTypeRef, pub tab_error: PyTypeRef, pub system_error: PyTypeRef, pub type_error: PyTypeRef, pub value_error: PyTypeRef, pub unicode_error: PyTypeRef, pub unicode_decode_error: PyTypeRef, pub unicode_encode_error: PyTypeRef, pub unicode_translate_error: PyTypeRef, #[cfg(feature = "jit")] pub jit_error: PyTypeRef, pub warning: PyTypeRef, pub deprecation_warning: PyTypeRef, pub pending_deprecation_warning: PyTypeRef, pub runtime_warning: PyTypeRef, pub syntax_warning: PyTypeRef, pub user_warning: PyTypeRef, pub future_warning: PyTypeRef, pub import_warning: PyTypeRef, pub unicode_warning: PyTypeRef, pub bytes_warning: PyTypeRef, pub resource_warning: PyTypeRef, } impl ExceptionZoo { pub(crate) fn init() -> Self { let base_exception_type = PyBaseException::init_bare_type().clone(); let create_exception_type = |name: &str, base: &PyTypeRef| { create_type_with_slots( name, PyType::static_type(), base, PyBaseException::make_slots(), ) }; // Sorted By Hierarchy then alphabetized. let system_exit = create_exception_type("SystemExit", &base_exception_type); let keyboard_interrupt = create_exception_type("KeyboardInterrupt", &base_exception_type); let generator_exit = create_exception_type("GeneratorExit", &base_exception_type); let exception_type = create_exception_type("Exception", &base_exception_type); let stop_iteration = create_exception_type("StopIteration", &exception_type); let stop_async_iteration = create_exception_type("StopAsyncIteration", &exception_type); let arithmetic_error = create_exception_type("ArithmeticError", &exception_type); let floating_point_error = create_exception_type("FloatingPointError", &arithmetic_error); let overflow_error = create_exception_type("OverflowError", &arithmetic_error); let zero_division_error = create_exception_type("ZeroDivisionError", &arithmetic_error); let assertion_error = create_exception_type("AssertionError", &exception_type); let attribute_error = create_exception_type("AttributeError", &exception_type); let buffer_error = create_exception_type("BufferError", &exception_type); let eof_error = create_exception_type("EOFError", &exception_type); let import_error = create_exception_type("ImportError", &exception_type); let module_not_found_error = create_exception_type("ModuleNotFoundError", &import_error); let lookup_error = create_exception_type("LookupError", &exception_type); let index_error = create_exception_type("IndexError", &lookup_error); let key_error = create_exception_type("KeyError", &lookup_error); let memory_error = create_exception_type("MemoryError", &exception_type); let name_error = create_exception_type("NameError", &exception_type); let unbound_local_error = create_exception_type("UnboundLocalError", &name_error); // os errors let os_error = create_exception_type("OSError", &exception_type); let blocking_io_error = create_exception_type("BlockingIOError", &os_error); let child_process_error = create_exception_type("ChildProcessError", &os_error); let connection_error = create_exception_type("ConnectionError", &os_error); let connection_aborted_error = create_exception_type("ConnectionAbortedError", &connection_error); let connection_refused_error = create_exception_type("ConnectionRefusedError", &connection_error); let connection_reset_error = create_exception_type("ConnectionResetError", &connection_error); let file_exists_error = create_exception_type("FileExistsError", &os_error); let file_not_found_error = create_exception_type("FileNotFoundError", &os_error); let interrupted_error = create_exception_type("InterruptedError", &os_error); let is_a_directory_error = create_exception_type("IsADirectoryError", &os_error); let not_a_directory_error = create_exception_type("NotADirectoryError", &os_error); let broken_pipe_error = create_exception_type("BrokenPipeError", &connection_error); let permission_error = create_exception_type("PermissionError", &os_error); let process_lookup_error = create_exception_type("ProcessLookupError", &os_error); let timeout_error = create_exception_type("TimeoutError", &os_error); let reference_error = create_exception_type("ReferenceError", &exception_type); let runtime_error = create_exception_type("RuntimeError", &exception_type); let not_implemented_error = create_exception_type("NotImplementedError", &runtime_error); let recursion_error = create_exception_type("RecursionError", &runtime_error); let syntax_error = create_exception_type("SyntaxError", &exception_type); let indentation_error = create_exception_type("IndentationError", &syntax_error); let tab_error = create_exception_type("TabError", &indentation_error); let target_scope_error = create_exception_type("TargetScopeError", &syntax_error); let system_error = create_exception_type("SystemError", &exception_type); let type_error = create_exception_type("TypeError", &exception_type); let value_error = create_exception_type("ValueError", &exception_type); let unicode_error = create_exception_type("UnicodeError", &value_error); let unicode_decode_error = create_exception_type("UnicodeDecodeError", &unicode_error); let unicode_encode_error = create_exception_type("UnicodeEncodeError", &unicode_error); let unicode_translate_error = create_exception_type("UnicodeTranslateError", &unicode_error); #[cfg(feature = "jit")] let jit_error = create_exception_type("JitError", &exception_type); let warning = create_exception_type("Warning", &exception_type); let deprecation_warning = create_exception_type("DeprecationWarning", &warning); let pending_deprecation_warning = create_exception_type("PendingDeprecationWarning", &warning); let runtime_warning = create_exception_type("RuntimeWarning", &warning); let syntax_warning = create_exception_type("SyntaxWarning", &warning); let user_warning = create_exception_type("UserWarning", &warning); let future_warning = create_exception_type("FutureWarning", &warning); let import_warning = create_exception_type("ImportWarning", &warning); let unicode_warning = create_exception_type("UnicodeWarning", &warning); let bytes_warning = create_exception_type("BytesWarning", &warning); let resource_warning = create_exception_type("ResourceWarning", &warning); Self { base_exception_type, system_exit, keyboard_interrupt, generator_exit, exception_type, stop_iteration, stop_async_iteration, arithmetic_error, floating_point_error, overflow_error, zero_division_error, assertion_error, attribute_error, buffer_error, eof_error, import_error, module_not_found_error, lookup_error, index_error, key_error, memory_error, name_error, unbound_local_error, os_error, blocking_io_error, child_process_error, connection_error, broken_pipe_error, connection_aborted_error, connection_refused_error, connection_reset_error, file_exists_error, file_not_found_error, interrupted_error, is_a_directory_error, not_a_directory_error, permission_error, process_lookup_error, timeout_error, reference_error, runtime_error, not_implemented_error, recursion_error, syntax_error, target_scope_error, indentation_error, tab_error, system_error, type_error, value_error, unicode_error, unicode_decode_error, unicode_encode_error, unicode_translate_error, #[cfg(feature = "jit")] jit_error, warning, deprecation_warning, pending_deprecation_warning, runtime_warning, syntax_warning, user_warning, future_warning, import_warning, unicode_warning, bytes_warning, resource_warning, } } pub fn extend(ctx: &PyContext) { let excs = &ctx.exceptions; PyBaseException::extend_class(ctx, &excs.base_exception_type); extend_class!(ctx, &excs.syntax_error, { "msg" => ctx.new_readonly_getset("msg", make_arg_getter(0)), // TODO: members "filename" => ctx.none(), "lineno" => ctx.none(), "offset" => ctx.none(), "text" => ctx.none(), }); extend_class!(ctx, &excs.system_exit, { "code" => ctx.new_readonly_getset("code", system_exit_code), }); extend_class!(ctx, &excs.import_error, { "__init__" => ctx.new_method(import_error_init), "msg" => ctx.new_readonly_getset("msg", make_arg_getter(0)), }); extend_class!(ctx, &excs.stop_iteration, { "value" => ctx.new_readonly_getset("value", make_arg_getter(0)), }); extend_class!(ctx, &excs.key_error, { "__str__" => ctx.new_method(key_error_str), }); extend_class!(ctx, &excs.unicode_decode_error, { "encoding" => ctx.new_readonly_getset("encoding", make_arg_getter(0)), "object" => ctx.new_readonly_getset("object", make_arg_getter(1)), "start" => ctx.new_readonly_getset("start", make_arg_getter(2)), "end" => ctx.new_readonly_getset("end", make_arg_getter(3)), "reason" => ctx.new_readonly_getset("reason", make_arg_getter(4)), }); extend_class!(ctx, &excs.unicode_encode_error, { "encoding" => ctx.new_readonly_getset("encoding", make_arg_getter(0)), "object" => ctx.new_readonly_getset("object", make_arg_getter(1)), "start" => ctx.new_readonly_getset("start", make_arg_getter(2)), "end" => ctx.new_readonly_getset("end", make_arg_getter(3)), "reason" => ctx.new_readonly_getset("reason", make_arg_getter(4)), }); extend_class!(ctx, &excs.unicode_translate_error, { "encoding" => ctx.new_readonly_getset("encoding", none_getter), "object" => ctx.new_readonly_getset("object", make_arg_getter(0)), "start" => ctx.new_readonly_getset("start", make_arg_getter(1)), "end" => ctx.new_readonly_getset("end", make_arg_getter(2)), "reason" => ctx.new_readonly_getset("reason", make_arg_getter(3)), }); } } fn import_error_init(exc_self: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { vm.set_attr( &exc_self, "name", vm.unwrap_or_none(args.kwargs.get("name").cloned()), )?; vm.set_attr( &exc_self, "path", vm.unwrap_or_none(args.kwargs.get("path").cloned()), )?; Ok(()) } fn none_getter(_obj: PyObjectRef, vm: &VirtualMachine) -> PyNoneRef { vm.ctx.none.clone() } fn make_arg_getter(idx: usize) -> impl Fn(PyBaseExceptionRef) -> Option { move |exc| exc.get_arg(idx) } fn key_error_str(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyStrRef { let args = exc.args(); if args.borrow_value().len() == 1 { exception_args_as_string(vm, args, false) .into_iter() .exactly_one() .unwrap() } else { exc.str(vm) } } fn system_exit_code(exc: PyBaseExceptionRef) -> Option { exc.args.read().borrow_value().first().map(|code| { match_class!(match code { ref tup @ PyTuple => match tup.borrow_value() { [x] => x.clone(), _ => code.clone(), }, other => other.clone(), }) }) } pub struct SerializeException<'s> { vm: &'s VirtualMachine, exc: &'s PyBaseExceptionRef, } impl<'s> SerializeException<'s> { pub fn new(vm: &'s VirtualMachine, exc: &'s PyBaseExceptionRef) -> Self { SerializeException { vm, exc } } } impl serde::Serialize for SerializeException<'_> { fn serialize(&self, s: S) -> Result { use serde::ser::*; let mut struc = s.serialize_struct("PyBaseException", 7)?; struc.serialize_field("exc_type", &self.exc.class().name)?; let tbs = { struct Tracebacks(PyTracebackRef); impl serde::Serialize for Tracebacks { fn serialize(&self, s: S) -> Result { let mut s = s.serialize_seq(None)?; for tb in self.0.iter() { s.serialize_element(&*tb)?; } s.end() } } self.exc.traceback().map(Tracebacks) }; struc.serialize_field("traceback", &tbs)?; struc.serialize_field( "cause", &self.exc.cause().as_ref().map(|e| Self::new(self.vm, e)), )?; struc.serialize_field( "context", &self.exc.context().as_ref().map(|e| Self::new(self.vm, e)), )?; struc.serialize_field("suppress_context", &self.exc.suppress_context.load())?; let args = { struct Args<'vm>(&'vm VirtualMachine, PyTupleRef); impl serde::Serialize for Args<'_> { fn serialize(&self, s: S) -> Result { s.collect_seq( self.1 .borrow_value() .iter() .map(|arg| py_serde::PyObjectSerializer::new(self.0, arg)), ) } } Args(self.vm, self.exc.args()) }; struc.serialize_field("args", &args)?; let rendered = { let mut rendered = Vec::::new(); write_exception(&mut rendered, self.vm, &self.exc).map_err(S::Error::custom)?; String::from_utf8(rendered).map_err(S::Error::custom)? }; struc.serialize_field("rendered", &rendered)?; struc.end() } }