Skip to main content

rustpython_vm/
exceptions.rs

1use self::types::{PyBaseException, PyBaseExceptionRef, PyException, PyMemoryError};
2pub use super::exception_group::exception_group;
3use crate::common::lock::PyRwLock;
4use crate::object::{Traverse, TraverseFn};
5use crate::{
6    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
7    VirtualMachine,
8    builtins::{
9        PyList, PyNone, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef,
10        traceback::{PyTraceback, PyTracebackRef},
11    },
12    class::{PyClassImpl, StaticType},
13    convert::{IntoPyException, ToPyException, ToPyObject},
14    function::{ArgIterable, FuncArgs, IntoFuncArgs, PySetterValue},
15    py_io::{self, Write},
16    stdlib::sys,
17    suggestion::offer_suggestions,
18    types::{Callable, Constructor, Initializer, Representable},
19};
20use core::fmt::{self, Display, Formatter};
21use core::sync::atomic::{AtomicBool, Ordering};
22use itertools::Itertools;
23#[cfg(feature = "host_env")]
24use std::io::{BufRead, BufReader};
25use std::sync::Mutex;
26use std::{collections::HashSet, io};
27
28unsafe impl Traverse for PyBaseException {
29    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
30        self.traceback.traverse(tracer_fn);
31        self.cause.traverse(tracer_fn);
32        self.context.traverse(tracer_fn);
33        self.args.traverse(tracer_fn);
34    }
35}
36
37impl core::fmt::Debug for PyBaseException {
38    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
39        // TODO: implement more detailed, non-recursive Debug formatter
40        f.write_str("PyBaseException")
41    }
42}
43
44const MEMORY_ERROR_FREELIST_SIZE: usize = 16;
45
46struct MemoryErrorHusk(*mut PyObject);
47unsafe impl Send for MemoryErrorHusk {}
48
49static MEMORY_ERROR_FREELIST: Mutex<[Option<MemoryErrorHusk>; MEMORY_ERROR_FREELIST_SIZE]> =
50    Mutex::new([const { None }; MEMORY_ERROR_FREELIST_SIZE]);
51
52impl PyPayload for PyBaseException {
53    #[inline]
54    fn class(ctx: &Context) -> &'static Py<PyType> {
55        ctx.exceptions.base_exception_type
56    }
57}
58
59impl PyPayload for PyMemoryError {
60    const PAYLOAD_TYPE_ID: core::any::TypeId = <PyException as PyPayload>::PAYLOAD_TYPE_ID;
61    const HAS_FREELIST: bool = true;
62    const MAX_FREELIST: usize = MEMORY_ERROR_FREELIST_SIZE;
63
64    #[inline]
65    unsafe fn validate_downcastable_from(obj: &PyObject) -> bool {
66        obj.class()
67            .fast_issubclass(<Self as StaticType>::static_type())
68    }
69
70    fn class(_ctx: &Context) -> &'static Py<PyType> {
71        <Self as StaticType>::static_type()
72    }
73
74    unsafe fn freelist_push(obj: *mut PyObject) -> bool {
75        let Ok(mut list) = MEMORY_ERROR_FREELIST.lock() else {
76            return false;
77        };
78        match list.iter_mut().find(|element| element.is_none()) {
79            Some(element) => {
80                *element = Some(MemoryErrorHusk(obj));
81                true
82            }
83            None => false,
84        }
85    }
86
87    unsafe fn freelist_pop(_payload: &Self) -> Option<core::ptr::NonNull<PyObject>> {
88        let husk = MEMORY_ERROR_FREELIST
89            .lock()
90            .ok()?
91            .iter_mut()
92            .find_map(|element| element.take())?;
93        core::ptr::NonNull::new(husk.0)
94    }
95}
96
97impl VirtualMachine {
98    // Why `impl VirtualMachine`?
99    // These functions are natively free function in CPython - not methods of PyException
100
101    /// Print exception chain by calling sys.excepthook
102    pub fn print_exception(&self, exc: &Py<PyBaseException>) {
103        let vm = self;
104        let write_fallback = |exc, errstr| {
105            if let Ok(stderr) = sys::get_stderr(vm) {
106                let mut stderr = py_io::PyWriter(stderr, vm);
107                // if this fails stderr might be closed -- ignore it
108                let _ = writeln!(stderr, "{errstr}");
109                let _ = self.write_exception(&mut stderr, exc);
110            } else {
111                eprintln!("{errstr}\nlost sys.stderr");
112                let _ = self.write_exception(&mut py_io::IoWriter(io::stderr()), exc);
113            }
114        };
115        if let Ok(excepthook) = vm.sys_module.get_attr("excepthook", vm) {
116            let (exc_type, exc_val, exc_tb) = vm.split_exception(exc.to_owned());
117            if let Err(eh_exc) = excepthook.call((exc_type, exc_val, exc_tb), vm) {
118                write_fallback(&eh_exc, "Error in sys.excepthook:");
119                write_fallback(exc, "Original exception was:");
120            }
121        } else {
122            write_fallback(exc, "missing sys.excepthook");
123        }
124    }
125
126    pub fn write_exception<W: Write>(
127        &self,
128        output: &mut W,
129        exc: &Py<PyBaseException>,
130    ) -> Result<(), W::Error> {
131        let seen = &mut HashSet::<usize>::new();
132        self.write_exception_recursive(output, exc, seen)
133    }
134
135    fn write_exception_recursive<W: Write>(
136        &self,
137        output: &mut W,
138        exc: &Py<PyBaseException>,
139        seen: &mut HashSet<usize>,
140    ) -> Result<(), W::Error> {
141        // This function should not be called directly,
142        // use `wite_exception` as a public interface.
143        // It is similar to `print_exception_recursive` from `CPython`.
144        seen.insert(exc.get_id());
145
146        #[allow(clippy::manual_map)]
147        if let Some((cause_or_context, msg)) = if let Some(cause) = exc.__cause__() {
148            // This can be a special case: `raise e from e`,
149            // we just ignore it and treat like `raise e` without any extra steps.
150            Some((
151                cause,
152                "\nThe above exception was the direct cause of the following exception:\n",
153            ))
154        } else if let Some(context) = exc.__context__() {
155            // This can be a special case:
156            //   e = ValueError('e')
157            //   e.__context__ = e
158            // In this case, we just ignore
159            // `__context__` part from going into recursion.
160            Some((
161                context,
162                "\nDuring handling of the above exception, another exception occurred:\n",
163            ))
164        } else {
165            None
166        } {
167            if !seen.contains(&cause_or_context.get_id()) {
168                self.write_exception_recursive(output, &cause_or_context, seen)?;
169                writeln!(output, "{msg}")?;
170            } else {
171                seen.insert(cause_or_context.get_id());
172            }
173        }
174
175        self.write_exception_inner(output, exc)
176    }
177
178    /// Print exception with traceback
179    pub fn write_exception_inner<W: Write>(
180        &self,
181        output: &mut W,
182        exc: &Py<PyBaseException>,
183    ) -> Result<(), W::Error> {
184        let vm = self;
185
186        let traceback = exc.traceback.read().clone();
187        if let Some(tb) = traceback {
188            writeln!(output, "Traceback (most recent call last):")?;
189            for tb in tb.iter() {
190                write_traceback_entry(output, &tb)?;
191            }
192        }
193
194        let varargs = exc.args();
195        let args_repr = vm.exception_args_as_string(&varargs, true);
196
197        let exc_class = exc.class();
198
199        if exc_class.fast_issubclass(vm.ctx.exceptions.syntax_error) {
200            return self.write_syntaxerror(output, exc, exc_class, &args_repr);
201        }
202
203        let exc_name = exc_class.name();
204        match args_repr.len() {
205            0 => write!(output, "{exc_name}"),
206            1 => write!(output, "{}: {}", exc_name, args_repr[0]),
207            _ => write!(
208                output,
209                "{}: ({})",
210                exc_name,
211                args_repr.into_iter().format(", "),
212            ),
213        }?;
214
215        if let Some(suggestions) = offer_suggestions(exc, vm) {
216            writeln!(output, ". Did you mean: '{suggestions}'?")
217        } else {
218            writeln!(output)
219        }
220    }
221
222    /// Format and write a SyntaxError
223    /// This logic is derived from TracebackException._format_syntax_error
224    ///
225    /// The logic has support for `end_offset` to highlight a range in the source code,
226    /// but it looks like `end_offset` is not used yet when SyntaxErrors are created.
227    fn write_syntaxerror<W: Write>(
228        &self,
229        output: &mut W,
230        exc: &Py<PyBaseException>,
231        exc_type: &Py<PyType>,
232        args_repr: &[PyRef<PyStr>],
233    ) -> Result<(), W::Error> {
234        let vm = self;
235        debug_assert!(exc_type.fast_issubclass(vm.ctx.exceptions.syntax_error));
236
237        let getattr = |attr: &'static str| exc.as_object().get_attr(attr, vm).ok();
238
239        let maybe_lineno = getattr("lineno").map(|obj| {
240            obj.str(vm)
241                .unwrap_or_else(|_| vm.ctx.new_str("<lineno str() failed>"))
242        });
243        let maybe_filename = getattr("filename").and_then(|obj| obj.str(vm).ok());
244
245        let maybe_text = getattr("text").map(|obj| {
246            obj.str(vm)
247                .unwrap_or_else(|_| vm.ctx.new_str("<text str() failed>"))
248        });
249
250        let mut filename_suffix = String::new();
251
252        if let Some(lineno) = maybe_lineno {
253            let filename = match maybe_filename {
254                Some(filename) => filename,
255                None => vm.ctx.new_str("<string>"),
256            };
257            writeln!(output, r##"  File "{filename}", line {lineno}"##,)?;
258        } else if let Some(filename) = maybe_filename {
259            filename_suffix = format!(" ({filename})");
260        }
261
262        if let Some(text) = maybe_text {
263            // if text ends with \n or \r\n, remove it
264            use rustpython_common::wtf8::CodePoint;
265            let text_wtf8 = text.as_wtf8();
266            let r_text = text_wtf8.trim_end_matches(|cp: CodePoint| {
267                cp == CodePoint::from_char('\n') || cp == CodePoint::from_char('\r')
268            });
269            let l_text = r_text.trim_start_matches(|cp: CodePoint| {
270                cp == CodePoint::from_char(' ')
271                    || cp == CodePoint::from_char('\n')
272                    || cp == CodePoint::from_char('\x0c') // \f
273            });
274            let spaces = (r_text.len() - l_text.len()) as isize;
275
276            writeln!(output, "    {l_text}")?;
277
278            let maybe_offset: Option<isize> =
279                getattr("offset").and_then(|obj| obj.try_to_value::<isize>(vm).ok());
280
281            if let Some(offset) = maybe_offset {
282                let maybe_end_offset: Option<isize> =
283                    getattr("end_offset").and_then(|obj| obj.try_to_value::<isize>(vm).ok());
284                let maybe_end_lineno: Option<isize> =
285                    getattr("end_lineno").and_then(|obj| obj.try_to_value::<isize>(vm).ok());
286                let maybe_lineno_int: Option<isize> =
287                    getattr("lineno").and_then(|obj| obj.try_to_value::<isize>(vm).ok());
288
289                // Only show caret if end_lineno is same as lineno (or not set)
290                let same_line = match (maybe_lineno_int, maybe_end_lineno) {
291                    (Some(lineno), Some(end_lineno)) => lineno == end_lineno,
292                    _ => true,
293                };
294
295                // A lone continuation at EOF has no highlighted source span.
296                let lone_line_continuation =
297                    maybe_end_offset == Some(-1) && l_text.to_string_lossy() == "\\";
298
299                if same_line && !lone_line_continuation {
300                    let mut end_offset = match maybe_end_offset {
301                        Some(0) | None => offset,
302                        Some(end_offset) => end_offset,
303                    };
304
305                    if offset == end_offset || end_offset == -1 {
306                        end_offset = offset + 1;
307                    }
308
309                    // Convert 1-based column offset to 0-based index into stripped text
310                    let colno = offset - 1 - spaces;
311                    let end_colno = end_offset - 1 - spaces;
312                    if colno >= 0 {
313                        let caret_space = l_text
314                            .code_points()
315                            .take(colno as usize)
316                            .map(|cp| cp.to_char().filter(|c| c.is_whitespace()).unwrap_or(' '))
317                            .collect::<String>();
318
319                        let mut error_width = end_colno - colno;
320                        if error_width < 1 {
321                            error_width = 1;
322                        }
323
324                        writeln!(
325                            output,
326                            "    {}{}",
327                            caret_space,
328                            "^".repeat(error_width as usize)
329                        )?;
330                    }
331                }
332            }
333        }
334
335        let exc_name = exc_type.name();
336
337        match args_repr.len() {
338            0 => write!(output, "{exc_name}{filename_suffix}"),
339            1 => write!(output, "{}: {}{}", exc_name, args_repr[0], filename_suffix),
340            _ => write!(
341                output,
342                "{}: ({}){}",
343                exc_name,
344                args_repr.iter().format(", "),
345                filename_suffix
346            ),
347        }?;
348
349        match offer_suggestions(exc, vm) {
350            Some(suggestions) => writeln!(output, ". Did you mean: '{suggestions}'?"),
351            None => writeln!(output),
352        }
353    }
354
355    fn exception_args_as_string(&self, varargs: &Py<PyTuple>, str_single: bool) -> Vec<PyStrRef> {
356        let vm = self;
357        match varargs.as_slice().len() {
358            0 => vec![],
359            1 => {
360                let args0_repr = if str_single {
361                    varargs.as_slice()[0]
362                        .str(vm)
363                        .unwrap_or_else(|_| PyStr::from("<element str() failed>").into_ref(&vm.ctx))
364                } else {
365                    varargs.as_slice()[0].repr(vm).unwrap_or_else(|_| {
366                        PyStr::from("<element repr() failed>").into_ref(&vm.ctx)
367                    })
368                };
369                vec![args0_repr]
370            }
371            _ => varargs
372                .as_slice()
373                .iter()
374                .map(|vararg| {
375                    vararg.repr(vm).unwrap_or_else(|_| {
376                        PyStr::from("<element repr() failed>").into_ref(&vm.ctx)
377                    })
378                })
379                .collect(),
380        }
381    }
382
383    pub fn split_exception(
384        &self,
385        exc: PyBaseExceptionRef,
386    ) -> (PyObjectRef, PyObjectRef, PyObjectRef) {
387        let tb = exc.traceback().to_pyobject(self);
388        let class = exc.class().to_owned();
389        (class.into(), exc.into(), tb)
390    }
391
392    /// Similar to PyErr_NormalizeException in CPython
393    pub fn normalize_exception(
394        &self,
395        exc_type: PyObjectRef,
396        exc_val: PyObjectRef,
397        exc_tb: PyObjectRef,
398    ) -> PyResult<PyBaseExceptionRef> {
399        let ctor = ExceptionCtor::try_from_object(self, exc_type)?;
400        let exc = ctor.instantiate_value(exc_val, self)?;
401        if let Some(tb) = Option::<PyTracebackRef>::try_from_object(self, exc_tb)? {
402            exc.set_traceback(Some(tb));
403        }
404        Ok(exc)
405    }
406
407    pub fn invoke_exception(
408        &self,
409        cls: &Py<PyType>,
410        args: Vec<PyObjectRef>,
411    ) -> PyResult<PyBaseExceptionRef> {
412        // TODO: fast-path built-in exceptions by directly instantiating them? Is that really worth it?
413        let res = PyType::call(cls, args.into_args(self), self)?;
414        res.downcast::<PyBaseException>().map_err(|obj| {
415            self.new_type_error(format!(
416                "calling {} should have returned an instance of BaseException, not {}",
417                cls,
418                obj.class()
419            ))
420        })
421    }
422}
423
424#[cfg(feature = "host_env")]
425fn print_source_line<W: Write>(
426    output: &mut W,
427    filename: &str,
428    lineno: usize,
429) -> Result<(), W::Error> {
430    // TODO: use io.open() method instead, when available, according to https://github.com/python/cpython/blob/main/Python/traceback.c#L393
431    // TODO: support different encodings
432    let file = match crate::host_env::fs::open(filename) {
433        Ok(file) => file,
434        Err(_) => return Ok(()),
435    };
436    let file = BufReader::new(file);
437
438    for (i, line) in file.lines().enumerate() {
439        if i + 1 == lineno {
440            if let Ok(line) = line {
441                // Indented with 4 spaces
442                writeln!(output, "    {}", line.trim_start())?;
443            }
444            return Ok(());
445        }
446    }
447
448    Ok(())
449}
450
451/// Print exception occurrence location from traceback element
452fn write_traceback_entry<W: Write>(
453    output: &mut W,
454    tb_entry: &Py<PyTraceback>,
455) -> Result<(), W::Error> {
456    let filename = tb_entry.frame.iframe().code().source_path().as_str();
457    writeln!(
458        output,
459        r##"  File "{}", line {}, in {}"##,
460        filename.trim_start_matches(r"\\?\"),
461        tb_entry.lineno,
462        tb_entry.frame.iframe().code().obj_name
463    )?;
464
465    #[cfg(feature = "host_env")]
466    print_source_line(output, filename, tb_entry.lineno.get())?;
467
468    Ok(())
469}
470
471#[derive(Clone)]
472pub enum ExceptionCtor {
473    Class(PyTypeRef),
474    Instance(PyBaseExceptionRef),
475}
476
477impl TryFromObject for ExceptionCtor {
478    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
479        obj.downcast::<PyType>()
480            .and_then(|cls| {
481                if cls.fast_issubclass(vm.ctx.exceptions.base_exception_type) {
482                    Ok(Self::Class(cls))
483                } else {
484                    Err(cls.into())
485                }
486            })
487            .or_else(|obj| obj.downcast::<PyBaseException>().map(Self::Instance))
488            .map_err(|obj| {
489                vm.new_type_error(format!(
490                    "exceptions must be classes or instances deriving from BaseException, not {}",
491                    obj.class().name()
492                ))
493            })
494    }
495}
496
497impl ExceptionCtor {
498    pub fn instantiate(self, vm: &VirtualMachine) -> PyResult<PyBaseExceptionRef> {
499        match self {
500            Self::Class(cls) => vm.invoke_exception(&cls, vec![]),
501            Self::Instance(exc) => Ok(exc),
502        }
503    }
504
505    pub fn instantiate_value(
506        self,
507        value: PyObjectRef,
508        vm: &VirtualMachine,
509    ) -> PyResult<PyBaseExceptionRef> {
510        let exc_inst = value.clone().downcast::<PyBaseException>().ok();
511        match (self, exc_inst) {
512            // both are instances; which would we choose?
513            (Self::Instance(_exc_a), Some(_exc_b)) => {
514                Err(vm.new_type_error("instance exception may not have a separate value"))
515            }
516            // if the "type" is an instance and the value isn't, use the "type"
517            (Self::Instance(exc), None) => Ok(exc),
518            // if the value is an instance of the type, use the instance value
519            (Self::Class(cls), Some(exc)) if exc.fast_isinstance(&cls) => Ok(exc),
520            // otherwise; construct an exception of the type using the value as args
521            (Self::Class(cls), _) => {
522                let args = match_class!(match value {
523                    PyNone => vec![],
524                    tup @ PyTuple => tup.as_slice().to_vec(),
525                    exc @ PyBaseException => exc.args().as_slice().to_vec(),
526                    obj => vec![obj],
527                });
528                vm.invoke_exception(&cls, args)
529            }
530        }
531    }
532}
533
534#[derive(Debug)]
535pub struct ExceptionZoo {
536    pub base_exception_type: &'static Py<PyType>,
537    pub base_exception_group: &'static Py<PyType>,
538    pub system_exit: &'static Py<PyType>,
539    pub keyboard_interrupt: &'static Py<PyType>,
540    pub generator_exit: &'static Py<PyType>,
541    pub exception_type: &'static Py<PyType>,
542    pub stop_iteration: &'static Py<PyType>,
543    pub stop_async_iteration: &'static Py<PyType>,
544    pub arithmetic_error: &'static Py<PyType>,
545    pub floating_point_error: &'static Py<PyType>,
546    pub overflow_error: &'static Py<PyType>,
547    pub zero_division_error: &'static Py<PyType>,
548    pub assertion_error: &'static Py<PyType>,
549    pub attribute_error: &'static Py<PyType>,
550    pub buffer_error: &'static Py<PyType>,
551    pub eof_error: &'static Py<PyType>,
552    pub import_error: &'static Py<PyType>,
553    pub module_not_found_error: &'static Py<PyType>,
554    pub lookup_error: &'static Py<PyType>,
555    pub index_error: &'static Py<PyType>,
556    pub key_error: &'static Py<PyType>,
557    pub memory_error: &'static Py<PyType>,
558    pub name_error: &'static Py<PyType>,
559    pub unbound_local_error: &'static Py<PyType>,
560    pub os_error: &'static Py<PyType>,
561    pub blocking_io_error: &'static Py<PyType>,
562    pub child_process_error: &'static Py<PyType>,
563    pub connection_error: &'static Py<PyType>,
564    pub broken_pipe_error: &'static Py<PyType>,
565    pub connection_aborted_error: &'static Py<PyType>,
566    pub connection_refused_error: &'static Py<PyType>,
567    pub connection_reset_error: &'static Py<PyType>,
568    pub file_exists_error: &'static Py<PyType>,
569    pub file_not_found_error: &'static Py<PyType>,
570    pub interrupted_error: &'static Py<PyType>,
571    pub is_a_directory_error: &'static Py<PyType>,
572    pub not_a_directory_error: &'static Py<PyType>,
573    pub permission_error: &'static Py<PyType>,
574    pub process_lookup_error: &'static Py<PyType>,
575    pub timeout_error: &'static Py<PyType>,
576    pub reference_error: &'static Py<PyType>,
577    pub runtime_error: &'static Py<PyType>,
578    pub not_implemented_error: &'static Py<PyType>,
579    pub recursion_error: &'static Py<PyType>,
580    pub python_finalization_error: &'static Py<PyType>,
581    pub syntax_error: &'static Py<PyType>,
582    pub incomplete_input_error: &'static Py<PyType>,
583    pub indentation_error: &'static Py<PyType>,
584    pub tab_error: &'static Py<PyType>,
585    pub system_error: &'static Py<PyType>,
586    pub type_error: &'static Py<PyType>,
587    pub value_error: &'static Py<PyType>,
588    pub unicode_error: &'static Py<PyType>,
589    pub unicode_decode_error: &'static Py<PyType>,
590    pub unicode_encode_error: &'static Py<PyType>,
591    pub unicode_translate_error: &'static Py<PyType>,
592
593    #[cfg(feature = "jit")]
594    pub jit_error: &'static Py<PyType>,
595
596    pub warning: &'static Py<PyType>,
597    pub deprecation_warning: &'static Py<PyType>,
598    pub pending_deprecation_warning: &'static Py<PyType>,
599    pub runtime_warning: &'static Py<PyType>,
600    pub syntax_warning: &'static Py<PyType>,
601    pub user_warning: &'static Py<PyType>,
602    pub future_warning: &'static Py<PyType>,
603    pub import_warning: &'static Py<PyType>,
604    pub unicode_warning: &'static Py<PyType>,
605    pub bytes_warning: &'static Py<PyType>,
606    pub resource_warning: &'static Py<PyType>,
607    pub encoding_warning: &'static Py<PyType>,
608}
609
610macro_rules! extend_exception {
611    (
612        $exc_struct:ident,
613        $ctx:expr,
614        $class:expr
615    ) => {
616        extend_exception!($exc_struct, $ctx, $class, {});
617    };
618    (
619        $exc_struct:ident,
620        $ctx:expr,
621        $class:expr,
622        { $($name:expr => $value:expr),* $(,)* }
623    ) => {
624        $exc_struct::extend_class($ctx, $class);
625        extend_class!($ctx, $class, {
626            $($name => $value,)*
627        });
628    };
629}
630
631impl PyBaseException {
632    pub(crate) fn new(args: Vec<PyObjectRef>, vm: &VirtualMachine) -> Self {
633        Self {
634            traceback: PyRwLock::new(None),
635            cause: PyRwLock::new(None),
636            context: PyRwLock::new(None),
637            suppress_context: AtomicBool::new(false),
638            args: PyRwLock::new(PyTuple::new_ref(args, &vm.ctx)),
639        }
640    }
641
642    pub fn get_arg(&self, idx: usize) -> Option<PyObjectRef> {
643        self.args.read().as_slice().get(idx).cloned()
644    }
645}
646
647impl PyBaseException {
648    pub fn set_traceback(&self, traceback: Option<PyTracebackRef>) {
649        *self.traceback.write() = traceback;
650    }
651
652    pub fn set_cause(&self, cause: Option<PyRef<Self>>) {
653        let mut c = self.cause.write();
654        self.set_suppress_context(true);
655        *c = cause;
656    }
657
658    pub fn set_context(&self, context: Option<PyRef<Self>>) {
659        *self.context.write() = context;
660    }
661
662    pub(super) fn __suppress_context__(&self) -> bool {
663        self.suppress_context.load(Ordering::Relaxed)
664    }
665
666    pub(super) fn set_suppress_context(&self, suppress_context: bool) {
667        self.suppress_context
668            .store(suppress_context, Ordering::Relaxed);
669    }
670
671    pub fn args(&self) -> PyTupleRef {
672        self.args.read().clone()
673    }
674    pub fn __traceback__(&self) -> Option<PyTracebackRef> {
675        self.traceback.read().clone()
676    }
677    pub fn __cause__(&self) -> Option<PyRef<Self>> {
678        self.cause.read().clone()
679    }
680    pub fn __context__(&self) -> Option<PyRef<Self>> {
681        self.context.read().clone()
682    }
683}
684
685#[pyclass(
686    with(Py, PyRef, Constructor, Initializer, Representable),
687    flags(BASETYPE, HAS_DICT)
688)]
689impl PyBaseException {}
690
691#[pyclass]
692impl Py<PyBaseException> {
693    #[inline]
694    pub fn traceback(&self) -> Option<PyTracebackRef> {
695        self.__traceback__()
696    }
697
698    #[pymethod]
699    pub(super) fn __str__(&self, vm: &VirtualMachine) -> PyStrRef {
700        let str_args = vm.exception_args_as_string(&self.args(), true);
701        match str_args.into_iter().exactly_one() {
702            Err(i) if i.len() == 0 => vm.ctx.empty_str.to_owned(),
703            Ok(s) => s,
704            Err(i) => PyStr::from(format!("({})", i.format(", "))).into_ref(&vm.ctx),
705        }
706    }
707
708    #[pymethod]
709    pub fn add_note(&self, note: PyStrRef, vm: &VirtualMachine) -> PyResult<()> {
710        let dict = crate::builtins::object::object_get_dict(self.as_object().to_owned(), vm)
711            .map_err(|_| vm.new_attribute_error("Exception object has no __dict__"))?;
712
713        let notes = if let Ok(notes) = dict.get_item("__notes__", vm) {
714            notes
715        } else {
716            let new_notes = vm.ctx.new_list(vec![]);
717            dict.set_item("__notes__", new_notes.clone().into(), vm)?;
718            new_notes.into()
719        };
720
721        let notes = notes
722            .downcast::<PyList>()
723            .map_err(|_| vm.new_type_error("Cannot add note: __notes__ is not a list"))?;
724
725        notes.borrow_vec_mut().push(note.into());
726        Ok(())
727    }
728
729    #[pymethod]
730    fn __reduce__(&self, vm: &VirtualMachine) -> PyTupleRef {
731        if let Some(dict) = self.as_object().dict().filter(|x| !x.is_empty()) {
732            vm.new_tuple((self.class().to_owned(), self.args(), dict))
733        } else {
734            vm.new_tuple((self.class().to_owned(), self.args()))
735        }
736    }
737
738    #[pymethod]
739    fn __setstate__(&self, state: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
740        if !vm.is_none(&state) {
741            let dict = state
742                .downcast::<crate::builtins::PyDict>()
743                .map_err(|_| vm.new_type_error("state is not a dictionary"))?;
744
745            for (key, value) in &dict {
746                let key_str = key.str(vm)?;
747                if key_str.as_bytes().starts_with(b"__") {
748                    continue;
749                }
750                self.as_object().set_attr(&key_str, value, vm)?;
751            }
752        }
753        Ok(vm.ctx.none())
754    }
755
756    #[pygetset]
757    fn __dict__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<crate::builtins::PyDictRef> {
758        crate::builtins::object::object_get_dict(zelf, vm)
759    }
760
761    #[pygetset(setter)]
762    fn set___dict__(zelf: PyObjectRef, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
763        crate::builtins::object::object_generic_set_dict(zelf, value, vm)
764    }
765
766    #[pygetset]
767    pub fn args(&self) -> PyTupleRef {
768        self.payload.args()
769    }
770
771    #[pygetset(setter)]
772    fn set_args(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
773        let PySetterValue::Assign(value) = value else {
774            return Err(vm.new_type_error("args may not be deleted"));
775        };
776        let args: ArgIterable = value.try_into_value(vm)?;
777        let args = args.iter(vm)?.collect::<PyResult<Vec<_>>>()?;
778        *self.args.write() = PyTuple::new_ref(args, &vm.ctx);
779        Ok(())
780    }
781
782    #[pygetset]
783    pub fn __traceback__(&self) -> Option<PyTracebackRef> {
784        self.payload.__traceback__()
785    }
786
787    #[pygetset(setter)]
788    fn set___traceback__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
789        let PySetterValue::Assign(value) = value else {
790            return Err(vm.new_type_error("__traceback__ may not be deleted"));
791        };
792        let traceback = if vm.is_none(&value) {
793            None
794        } else {
795            match value.downcast::<PyTraceback>() {
796                Ok(tb) => Some(tb),
797                Err(_) => {
798                    return Err(vm.new_type_error("__traceback__ must be a traceback or None"));
799                }
800            }
801        };
802        self.set_traceback(traceback);
803        Ok(())
804    }
805
806    #[pygetset]
807    pub fn __cause__(&self) -> Option<PyRef<PyBaseException>> {
808        self.payload.__cause__()
809    }
810
811    #[pygetset(setter)]
812    fn set___cause__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
813        let PySetterValue::Assign(value) = value else {
814            return Err(vm.new_type_error("__cause__ may not be deleted"));
815        };
816        let cause = if vm.is_none(&value) {
817            None
818        } else {
819            match value.downcast::<PyBaseException>() {
820                Ok(exc) => Some(exc),
821                Err(_) => {
822                    return Err(vm.new_type_error(
823                        "exception cause must be None or derive from BaseException",
824                    ));
825                }
826            }
827        };
828        self.set_cause(cause);
829        Ok(())
830    }
831
832    #[pygetset]
833    pub fn __context__(&self) -> Option<PyRef<PyBaseException>> {
834        self.payload.__context__()
835    }
836
837    #[pygetset(setter)]
838    fn set___context__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
839        let PySetterValue::Assign(value) = value else {
840            return Err(vm.new_type_error("__context__ may not be deleted"));
841        };
842        let context = if vm.is_none(&value) {
843            None
844        } else {
845            match value.downcast::<PyBaseException>() {
846                Ok(exc) => Some(exc),
847                Err(_) => {
848                    return Err(vm.new_type_error(
849                        "exception context must be None or derive from BaseException",
850                    ));
851                }
852            }
853        };
854        self.set_context(context);
855        Ok(())
856    }
857}
858
859#[pyclass]
860impl PyRef<PyBaseException> {
861    #[pymethod]
862    fn with_traceback(self, tb: Option<PyTracebackRef>) -> Self {
863        *self.traceback.write() = tb;
864        self
865    }
866}
867
868impl Constructor for PyBaseException {
869    type Args = FuncArgs;
870
871    fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
872        if cls.is(Self::class(&vm.ctx)) && !args.kwargs.is_empty() {
873            return Err(vm.new_type_error("BaseException() takes no keyword arguments"));
874        }
875        Self::new(args.args, vm)
876            .into_ref_with_type_lazy_dict(vm, cls)
877            .map(Into::into)
878    }
879
880    fn py_new(_cls: &Py<PyType>, _args: FuncArgs, _vm: &VirtualMachine) -> PyResult<Self> {
881        unimplemented!("use slot_new")
882    }
883}
884
885impl Initializer for PyBaseException {
886    type Args = FuncArgs;
887
888    fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
889        *zelf.args.write() = PyTuple::new_ref(args.args, &vm.ctx);
890        Ok(())
891    }
892}
893
894impl Representable for PyBaseException {
895    #[inline]
896    fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
897        let repr_args = vm.exception_args_as_string(&zelf.args(), false);
898        let cls = zelf.class();
899        Ok(format!("{}({})", cls.name(), repr_args.iter().format(", ")))
900    }
901}
902
903impl ExceptionZoo {
904    pub(crate) fn init() -> Self {
905        use self::types::*;
906
907        let base_exception_type = PyBaseException::init_builtin_type();
908
909        // Sorted By Hierarchy then alphabetized.
910        let base_exception_group = PyBaseExceptionGroup::init_builtin_type();
911        let system_exit = PySystemExit::init_builtin_type();
912        let keyboard_interrupt = PyKeyboardInterrupt::init_builtin_type();
913        let generator_exit = PyGeneratorExit::init_builtin_type();
914
915        let exception_type = PyException::init_builtin_type();
916        let stop_iteration = PyStopIteration::init_builtin_type();
917        let stop_async_iteration = PyStopAsyncIteration::init_builtin_type();
918        let arithmetic_error = PyArithmeticError::init_builtin_type();
919        let floating_point_error = PyFloatingPointError::init_builtin_type();
920        let overflow_error = PyOverflowError::init_builtin_type();
921        let zero_division_error = PyZeroDivisionError::init_builtin_type();
922
923        let assertion_error = PyAssertionError::init_builtin_type();
924        let attribute_error = PyAttributeError::init_builtin_type();
925        let buffer_error = PyBufferError::init_builtin_type();
926        let eof_error = PyEOFError::init_builtin_type();
927
928        let import_error = PyImportError::init_builtin_type();
929        let module_not_found_error = PyModuleNotFoundError::init_builtin_type();
930
931        let lookup_error = PyLookupError::init_builtin_type();
932        let index_error = PyIndexError::init_builtin_type();
933        let key_error = PyKeyError::init_builtin_type();
934
935        let memory_error = PyMemoryError::init_builtin_type();
936
937        let name_error = PyNameError::init_builtin_type();
938        let unbound_local_error = PyUnboundLocalError::init_builtin_type();
939
940        // os errors
941        let os_error = PyOSError::init_builtin_type();
942        let blocking_io_error = PyBlockingIOError::init_builtin_type();
943        let child_process_error = PyChildProcessError::init_builtin_type();
944
945        let connection_error = PyConnectionError::init_builtin_type();
946        let broken_pipe_error = PyBrokenPipeError::init_builtin_type();
947        let connection_aborted_error = PyConnectionAbortedError::init_builtin_type();
948        let connection_refused_error = PyConnectionRefusedError::init_builtin_type();
949        let connection_reset_error = PyConnectionResetError::init_builtin_type();
950
951        let file_exists_error = PyFileExistsError::init_builtin_type();
952        let file_not_found_error = PyFileNotFoundError::init_builtin_type();
953        let interrupted_error = PyInterruptedError::init_builtin_type();
954        let is_a_directory_error = PyIsADirectoryError::init_builtin_type();
955        let not_a_directory_error = PyNotADirectoryError::init_builtin_type();
956        let permission_error = PyPermissionError::init_builtin_type();
957        let process_lookup_error = PyProcessLookupError::init_builtin_type();
958        let timeout_error = PyTimeoutError::init_builtin_type();
959
960        let reference_error = PyReferenceError::init_builtin_type();
961
962        let runtime_error = PyRuntimeError::init_builtin_type();
963        let not_implemented_error = PyNotImplementedError::init_builtin_type();
964        let recursion_error = PyRecursionError::init_builtin_type();
965        let python_finalization_error = PyPythonFinalizationError::init_builtin_type();
966
967        let syntax_error = PySyntaxError::init_builtin_type();
968        let incomplete_input_error = PyIncompleteInputError::init_builtin_type();
969        let indentation_error = PyIndentationError::init_builtin_type();
970        let tab_error = PyTabError::init_builtin_type();
971
972        let system_error = PySystemError::init_builtin_type();
973        let type_error = PyTypeError::init_builtin_type();
974        let value_error = PyValueError::init_builtin_type();
975        let unicode_error = PyUnicodeError::init_builtin_type();
976        let unicode_decode_error = PyUnicodeDecodeError::init_builtin_type();
977        let unicode_encode_error = PyUnicodeEncodeError::init_builtin_type();
978        let unicode_translate_error = PyUnicodeTranslateError::init_builtin_type();
979
980        #[cfg(feature = "jit")]
981        let jit_error = PyJitError::init_builtin_type();
982
983        let warning = PyWarning::init_builtin_type();
984        let deprecation_warning = PyDeprecationWarning::init_builtin_type();
985        let pending_deprecation_warning = PyPendingDeprecationWarning::init_builtin_type();
986        let runtime_warning = PyRuntimeWarning::init_builtin_type();
987        let syntax_warning = PySyntaxWarning::init_builtin_type();
988        let user_warning = PyUserWarning::init_builtin_type();
989        let future_warning = PyFutureWarning::init_builtin_type();
990        let import_warning = PyImportWarning::init_builtin_type();
991        let unicode_warning = PyUnicodeWarning::init_builtin_type();
992        let bytes_warning = PyBytesWarning::init_builtin_type();
993        let resource_warning = PyResourceWarning::init_builtin_type();
994        let encoding_warning = PyEncodingWarning::init_builtin_type();
995
996        Self {
997            base_exception_type,
998            base_exception_group,
999            system_exit,
1000            keyboard_interrupt,
1001            generator_exit,
1002            exception_type,
1003            stop_iteration,
1004            stop_async_iteration,
1005            arithmetic_error,
1006            floating_point_error,
1007            overflow_error,
1008            zero_division_error,
1009            assertion_error,
1010            attribute_error,
1011            buffer_error,
1012            eof_error,
1013            import_error,
1014            module_not_found_error,
1015            lookup_error,
1016            index_error,
1017            key_error,
1018            memory_error,
1019            name_error,
1020            unbound_local_error,
1021            os_error,
1022            blocking_io_error,
1023            child_process_error,
1024            connection_error,
1025            broken_pipe_error,
1026            connection_aborted_error,
1027            connection_refused_error,
1028            connection_reset_error,
1029            file_exists_error,
1030            file_not_found_error,
1031            interrupted_error,
1032            is_a_directory_error,
1033            not_a_directory_error,
1034            permission_error,
1035            process_lookup_error,
1036            timeout_error,
1037            reference_error,
1038            runtime_error,
1039            not_implemented_error,
1040            recursion_error,
1041            python_finalization_error,
1042            syntax_error,
1043            incomplete_input_error,
1044            indentation_error,
1045            tab_error,
1046            system_error,
1047            type_error,
1048            value_error,
1049            unicode_error,
1050            unicode_decode_error,
1051            unicode_encode_error,
1052            unicode_translate_error,
1053
1054            #[cfg(feature = "jit")]
1055            jit_error,
1056
1057            warning,
1058            deprecation_warning,
1059            pending_deprecation_warning,
1060            runtime_warning,
1061            syntax_warning,
1062            user_warning,
1063            future_warning,
1064            import_warning,
1065            unicode_warning,
1066            bytes_warning,
1067            resource_warning,
1068            encoding_warning,
1069        }
1070    }
1071
1072    // TODO: remove it after fixing `errno` / `winerror` problem
1073    #[allow(
1074        clippy::redundant_clone,
1075        reason = "temporary workaround until errno/winerror handling is fixed"
1076    )]
1077    pub fn extend(ctx: &'static Context) {
1078        use self::types::*;
1079
1080        let excs = &ctx.exceptions;
1081
1082        PyBaseException::extend_class(ctx, excs.base_exception_type);
1083
1084        // Sorted By Hierarchy then alphabetized.
1085        extend_exception!(PyBaseExceptionGroup, ctx, excs.base_exception_group);
1086
1087        extend_exception!(PySystemExit, ctx, excs.system_exit);
1088        extend_exception!(PyKeyboardInterrupt, ctx, excs.keyboard_interrupt);
1089        extend_exception!(PyGeneratorExit, ctx, excs.generator_exit);
1090
1091        extend_exception!(PyException, ctx, excs.exception_type);
1092
1093        extend_exception!(PyStopIteration, ctx, excs.stop_iteration);
1094        extend_exception!(PyStopAsyncIteration, ctx, excs.stop_async_iteration);
1095
1096        extend_exception!(PyArithmeticError, ctx, excs.arithmetic_error);
1097        extend_exception!(PyFloatingPointError, ctx, excs.floating_point_error);
1098        extend_exception!(PyOverflowError, ctx, excs.overflow_error);
1099        extend_exception!(PyZeroDivisionError, ctx, excs.zero_division_error);
1100
1101        extend_exception!(PyAssertionError, ctx, excs.assertion_error);
1102        extend_exception!(PyAttributeError, ctx, excs.attribute_error);
1103        extend_exception!(PyBufferError, ctx, excs.buffer_error);
1104        extend_exception!(PyEOFError, ctx, excs.eof_error);
1105
1106        extend_exception!(PyImportError, ctx, excs.import_error);
1107        extend_exception!(PyModuleNotFoundError, ctx, excs.module_not_found_error);
1108
1109        extend_exception!(PyLookupError, ctx, excs.lookup_error);
1110        extend_exception!(PyIndexError, ctx, excs.index_error);
1111
1112        extend_exception!(PyKeyError, ctx, excs.key_error);
1113
1114        extend_exception!(PyMemoryError, ctx, excs.memory_error);
1115        extend_exception!(PyNameError, ctx, excs.name_error);
1116        extend_exception!(PyUnboundLocalError, ctx, excs.unbound_local_error);
1117
1118        // os errors:
1119        // PyOSError now uses struct fields with pygetset, no need for dynamic attributes
1120        extend_exception!(PyOSError, ctx, excs.os_error);
1121
1122        extend_exception!(PyBlockingIOError, ctx, excs.blocking_io_error);
1123        extend_exception!(PyChildProcessError, ctx, excs.child_process_error);
1124
1125        extend_exception!(PyConnectionError, ctx, excs.connection_error);
1126        extend_exception!(PyBrokenPipeError, ctx, excs.broken_pipe_error);
1127        extend_exception!(PyConnectionAbortedError, ctx, excs.connection_aborted_error);
1128        extend_exception!(PyConnectionRefusedError, ctx, excs.connection_refused_error);
1129        extend_exception!(PyConnectionResetError, ctx, excs.connection_reset_error);
1130
1131        extend_exception!(PyFileExistsError, ctx, excs.file_exists_error);
1132        extend_exception!(PyFileNotFoundError, ctx, excs.file_not_found_error);
1133        extend_exception!(PyInterruptedError, ctx, excs.interrupted_error);
1134        extend_exception!(PyIsADirectoryError, ctx, excs.is_a_directory_error);
1135        extend_exception!(PyNotADirectoryError, ctx, excs.not_a_directory_error);
1136        extend_exception!(PyPermissionError, ctx, excs.permission_error);
1137        extend_exception!(PyProcessLookupError, ctx, excs.process_lookup_error);
1138        extend_exception!(PyTimeoutError, ctx, excs.timeout_error);
1139
1140        extend_exception!(PyReferenceError, ctx, excs.reference_error);
1141        extend_exception!(PyRuntimeError, ctx, excs.runtime_error);
1142        extend_exception!(PyNotImplementedError, ctx, excs.not_implemented_error);
1143        extend_exception!(PyRecursionError, ctx, excs.recursion_error);
1144        extend_exception!(
1145            PyPythonFinalizationError,
1146            ctx,
1147            excs.python_finalization_error
1148        );
1149
1150        extend_exception!(PySyntaxError, ctx, excs.syntax_error);
1151        extend_exception!(PyIncompleteInputError, ctx, excs.incomplete_input_error);
1152        extend_exception!(PyIndentationError, ctx, excs.indentation_error);
1153        extend_exception!(PyTabError, ctx, excs.tab_error);
1154
1155        extend_exception!(PySystemError, ctx, excs.system_error);
1156        extend_exception!(PyTypeError, ctx, excs.type_error);
1157        extend_exception!(PyValueError, ctx, excs.value_error);
1158        extend_exception!(PyUnicodeError, ctx, excs.unicode_error);
1159        extend_exception!(PyUnicodeDecodeError, ctx, excs.unicode_decode_error);
1160        extend_exception!(PyUnicodeEncodeError, ctx, excs.unicode_encode_error);
1161        extend_exception!(PyUnicodeTranslateError, ctx, excs.unicode_translate_error);
1162
1163        #[cfg(feature = "jit")]
1164        extend_exception!(PyJitError, ctx, excs.jit_error);
1165
1166        extend_exception!(PyWarning, ctx, excs.warning);
1167        extend_exception!(PyDeprecationWarning, ctx, excs.deprecation_warning);
1168        extend_exception!(
1169            PyPendingDeprecationWarning,
1170            ctx,
1171            excs.pending_deprecation_warning
1172        );
1173        extend_exception!(PyRuntimeWarning, ctx, excs.runtime_warning);
1174        extend_exception!(PySyntaxWarning, ctx, excs.syntax_warning);
1175        extend_exception!(PyUserWarning, ctx, excs.user_warning);
1176        extend_exception!(PyFutureWarning, ctx, excs.future_warning);
1177        extend_exception!(PyImportWarning, ctx, excs.import_warning);
1178        extend_exception!(PyUnicodeWarning, ctx, excs.unicode_warning);
1179        extend_exception!(PyBytesWarning, ctx, excs.bytes_warning);
1180        extend_exception!(PyResourceWarning, ctx, excs.resource_warning);
1181        extend_exception!(PyEncodingWarning, ctx, excs.encoding_warning);
1182    }
1183}
1184
1185#[cfg(feature = "serde")]
1186pub struct SerializeException<'vm, 's> {
1187    vm: &'vm VirtualMachine,
1188    exc: &'s Py<PyBaseException>,
1189}
1190
1191#[cfg(feature = "serde")]
1192impl<'vm, 's> SerializeException<'vm, 's> {
1193    pub fn new(vm: &'vm VirtualMachine, exc: &'s Py<PyBaseException>) -> Self {
1194        SerializeException { vm, exc }
1195    }
1196}
1197
1198#[cfg(feature = "serde")]
1199pub struct SerializeExceptionOwned<'vm> {
1200    vm: &'vm VirtualMachine,
1201    exc: PyBaseExceptionRef,
1202}
1203
1204#[cfg(feature = "serde")]
1205impl serde::Serialize for SerializeExceptionOwned<'_> {
1206    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1207        let Self { vm, exc } = self;
1208        SerializeException::new(vm, exc).serialize(s)
1209    }
1210}
1211
1212#[cfg(feature = "serde")]
1213impl serde::Serialize for SerializeException<'_, '_> {
1214    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1215        use serde::ser::*;
1216
1217        let mut struc = s.serialize_struct("PyBaseException", 7)?;
1218        struc.serialize_field("exc_type", &*self.exc.class().name())?;
1219        let tbs = {
1220            struct Tracebacks(PyTracebackRef);
1221            impl serde::Serialize for Tracebacks {
1222                fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1223                    let mut s = s.serialize_seq(None)?;
1224                    for tb in self.0.iter() {
1225                        s.serialize_element(&**tb)?;
1226                    }
1227                    s.end()
1228                }
1229            }
1230            self.exc.traceback().map(Tracebacks)
1231        };
1232        struc.serialize_field("traceback", &tbs)?;
1233        struc.serialize_field(
1234            "cause",
1235            &self
1236                .exc
1237                .__cause__()
1238                .map(|exc| SerializeExceptionOwned { vm: self.vm, exc }),
1239        )?;
1240        struc.serialize_field(
1241            "context",
1242            &self
1243                .exc
1244                .__context__()
1245                .map(|exc| SerializeExceptionOwned { vm: self.vm, exc }),
1246        )?;
1247        struc.serialize_field("suppress_context", &self.exc.__suppress_context__())?;
1248
1249        let args = {
1250            struct Args<'vm>(&'vm VirtualMachine, PyTupleRef);
1251            impl serde::Serialize for Args<'_> {
1252                fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1253                    s.collect_seq(
1254                        self.1
1255                            .as_slice()
1256                            .iter()
1257                            .map(|arg| crate::py_serde::PyObjectSerializer::new(self.0, arg)),
1258                    )
1259                }
1260            }
1261            Args(self.vm, self.exc.args())
1262        };
1263        struc.serialize_field("args", &args)?;
1264
1265        let rendered = {
1266            let mut rendered = String::new();
1267            self.vm
1268                .write_exception(&mut rendered, self.exc)
1269                .map_err(S::Error::custom)?;
1270            rendered
1271        };
1272        struc.serialize_field("rendered", &rendered)?;
1273
1274        struc.end()
1275    }
1276}
1277
1278#[derive(Debug)]
1279pub struct NulError;
1280
1281impl Display for NulError {
1282    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1283        write!(f, "embedded null character")
1284    }
1285}
1286
1287pub fn nul_char_error(vm: &VirtualMachine) -> PyBaseExceptionRef {
1288    vm.new_value_error("embedded null character")
1289}
1290
1291pub fn nul_char_type_error(vm: &VirtualMachine) -> PyBaseExceptionRef {
1292    vm.new_type_error("embedded null character")
1293}
1294
1295pub fn nul_byte_error(vm: &VirtualMachine) -> PyBaseExceptionRef {
1296    vm.new_value_error("embedded null byte")
1297}
1298
1299impl ToPyException for alloc::ffi::NulError {
1300    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1301        nul_char_error(vm)
1302    }
1303}
1304
1305impl ToPyException for alloc::ffi::FromVecWithNulError {
1306    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1307        nul_char_error(vm)
1308    }
1309}
1310
1311impl ToPyException for core::ffi::FromBytesWithNulError {
1312    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1313        nul_char_error(vm)
1314    }
1315}
1316
1317impl ToPyException for NulError {
1318    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1319        nul_char_error(vm)
1320    }
1321}
1322
1323#[cfg(windows)]
1324impl<C> ToPyException for widestring::error::ContainsNul<C> {
1325    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1326        nul_char_error(vm)
1327    }
1328}
1329
1330#[cfg(windows)]
1331impl ToPyException for widestring::error::MissingNulTerminator {
1332    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1333        nul_char_error(vm)
1334    }
1335}
1336
1337#[cfg(windows)]
1338impl<C> ToPyException for widestring::error::NulError<C> {
1339    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1340        nul_char_error(vm)
1341    }
1342}
1343
1344#[cfg(any(unix, windows, target_os = "wasi"))]
1345pub(crate) fn errno_to_exc_type(errno: i32, vm: &VirtualMachine) -> Option<&'static Py<PyType>> {
1346    use crate::stdlib::errno::errors;
1347    let excs = &vm.ctx.exceptions;
1348    match errno {
1349        #[allow(unreachable_patterns)] // EAGAIN is sometimes the same as EWOULDBLOCK
1350        errors::EWOULDBLOCK | errors::EAGAIN => Some(excs.blocking_io_error),
1351        errors::EALREADY => Some(excs.blocking_io_error),
1352        errors::EINPROGRESS => Some(excs.blocking_io_error),
1353        errors::EPIPE => Some(excs.broken_pipe_error),
1354        #[cfg(not(target_os = "wasi"))]
1355        errors::ESHUTDOWN => Some(excs.broken_pipe_error),
1356        errors::ECHILD => Some(excs.child_process_error),
1357        errors::ECONNABORTED => Some(excs.connection_aborted_error),
1358        errors::ECONNREFUSED => Some(excs.connection_refused_error),
1359        errors::ECONNRESET => Some(excs.connection_reset_error),
1360        errors::EEXIST => Some(excs.file_exists_error),
1361        errors::ENOENT => Some(excs.file_not_found_error),
1362        errors::EISDIR => Some(excs.is_a_directory_error),
1363        errors::ENOTDIR => Some(excs.not_a_directory_error),
1364        errors::EINTR => Some(excs.interrupted_error),
1365        errors::EACCES => Some(excs.permission_error),
1366        errors::EPERM => Some(excs.permission_error),
1367        // A process without the capability to reach a resource.
1368        #[cfg(target_vendor = "apple")]
1369        errors::ENOTCAPABLE => Some(excs.permission_error),
1370        errors::ESRCH => Some(excs.process_lookup_error),
1371        errors::ETIMEDOUT => Some(excs.timeout_error),
1372        _ => None,
1373    }
1374}
1375
1376#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
1377pub(crate) fn errno_to_exc_type(_errno: i32, _vm: &VirtualMachine) -> Option<&'static Py<PyType>> {
1378    None
1379}
1380
1381pub(crate) trait ToOSErrorBuilder {
1382    fn to_os_error_builder(&self, vm: &VirtualMachine) -> OSErrorBuilder;
1383}
1384
1385pub(crate) struct OSErrorBuilder {
1386    exc_type: PyTypeRef,
1387    errno: Option<i32>,
1388    strerror: Option<PyObjectRef>,
1389    filename: Option<PyObjectRef>,
1390    #[cfg(windows)]
1391    winerror: Option<PyObjectRef>,
1392    filename2: Option<PyObjectRef>,
1393}
1394
1395impl OSErrorBuilder {
1396    #[must_use]
1397    pub(crate) fn with_subtype(
1398        exc_type: PyTypeRef,
1399        errno: Option<i32>,
1400        strerror: impl ToPyObject,
1401        vm: &VirtualMachine,
1402    ) -> Self {
1403        let strerror = strerror.to_pyobject(vm);
1404        Self {
1405            exc_type,
1406            errno,
1407            strerror: Some(strerror),
1408            filename: None,
1409            #[cfg(windows)]
1410            winerror: None,
1411            filename2: None,
1412        }
1413    }
1414
1415    #[must_use]
1416    pub(crate) fn with_errno(errno: i32, strerror: impl ToPyObject, vm: &VirtualMachine) -> Self {
1417        let exc_type = errno_to_exc_type(errno, vm)
1418            .unwrap_or(vm.ctx.exceptions.os_error)
1419            .to_owned();
1420        Self::with_subtype(exc_type, Some(errno), strerror, vm)
1421    }
1422
1423    #[must_use]
1424    #[allow(dead_code)]
1425    pub(crate) fn filename(mut self, filename: PyObjectRef) -> Self {
1426        self.filename.replace(filename);
1427        self
1428    }
1429
1430    #[must_use]
1431    #[allow(dead_code)]
1432    pub(crate) fn filename2(mut self, filename: PyObjectRef) -> Self {
1433        self.filename2.replace(filename);
1434        self
1435    }
1436
1437    #[must_use]
1438    #[cfg(windows)]
1439    pub(crate) fn winerror(mut self, winerror: PyObjectRef) -> Self {
1440        self.winerror.replace(winerror);
1441        self
1442    }
1443
1444    /// Strip winerror from the builder. Used for C runtime errors
1445    /// (e.g. `_wopen`, `open`) that should produce `[Errno X]` format
1446    /// instead of `[WinError X]`.
1447    #[must_use]
1448    #[cfg(windows)]
1449    pub(crate) fn without_winerror(mut self) -> Self {
1450        self.winerror = None;
1451        self
1452    }
1453
1454    pub(crate) fn build(self, vm: &VirtualMachine) -> PyRef<types::PyOSError> {
1455        use types::PyOSError;
1456
1457        let Self {
1458            exc_type,
1459            errno,
1460            strerror,
1461            filename,
1462            #[cfg(windows)]
1463            winerror,
1464            filename2,
1465        } = self;
1466
1467        let args = if let Some(errno) = errno {
1468            let winerror = cfg_select! {
1469                windows => winerror.to_pyobject(vm),
1470                _ => vm.ctx.none(),
1471            };
1472
1473            vec![
1474                errno.to_pyobject(vm),
1475                strerror.to_pyobject(vm),
1476                filename.to_pyobject(vm),
1477                winerror,
1478                filename2.to_pyobject(vm),
1479            ]
1480        } else {
1481            vec![strerror.to_pyobject(vm)]
1482        };
1483
1484        vm.new_payload_exception::<PyOSError>(exc_type, args.into())
1485            .expect("new_os_error usage error")
1486    }
1487}
1488
1489impl IntoPyException for OSErrorBuilder {
1490    fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1491        self.build(vm).upcast()
1492    }
1493}
1494
1495impl ToOSErrorBuilder for std::io::Error {
1496    fn to_os_error_builder(&self, vm: &VirtualMachine) -> OSErrorBuilder {
1497        use crate::host_env::os::ErrorExt;
1498
1499        let errno = self.posix_errno();
1500        #[cfg(windows)]
1501        let msg = 'msg: {
1502            // Use C runtime's strerror for POSIX errno values.
1503            // For Windows-specific error codes, fall back to FormatMessage.
1504            const MAX_POSIX_ERRNO: i32 = 127;
1505            if errno > 0
1506                && errno <= MAX_POSIX_ERRNO
1507                && let Some(s) = crate::host_env::errno::strerror_string(errno)
1508                && !s.starts_with("Unknown error")
1509            {
1510                break 'msg s;
1511            }
1512            self.to_string()
1513        };
1514        #[cfg(unix)]
1515        let msg =
1516            crate::host_env::errno::strerror_string(errno).unwrap_or_else(|| self.to_string());
1517        #[cfg(not(any(windows, unix)))]
1518        let msg = self.to_string();
1519
1520        #[allow(unused_mut)]
1521        let mut builder = OSErrorBuilder::with_errno(errno, msg, vm);
1522        #[cfg(windows)]
1523        if let Some(winerror) = self.raw_os_error() {
1524            builder = builder.winerror(winerror.to_pyobject(vm));
1525        }
1526        builder
1527    }
1528}
1529
1530impl ToPyException for std::io::Error {
1531    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1532        let builder = self.to_os_error_builder(vm);
1533        builder.into_pyexception(vm)
1534    }
1535}
1536
1537impl IntoPyException for std::io::Error {
1538    fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1539        self.to_pyexception(vm)
1540    }
1541}
1542
1543#[cfg(all(unix, not(target_os = "redox")))]
1544impl ToPyException for rustpython_host_env::fcntl::LockfError {
1545    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1546        match self {
1547            Self::InvalidCmd => vm.new_value_error("unrecognized lockf argument"),
1548            Self::Overflow(e) => vm.new_overflow_error(e.clone()),
1549            Self::Io(err) => err.to_pyexception(vm),
1550        }
1551    }
1552}
1553
1554#[cfg(unix)]
1555impl ToPyException for rustpython_host_env::posix::AccessError {
1556    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1557        match self {
1558            Self::InvalidMode => vm.new_value_error(
1559                "One of the flags is wrong, there are only 4 possibilities F_OK, R_OK, W_OK and X_OK",
1560            ),
1561            Self::Os(errno) => std::io::Error::from_raw_os_error(*errno).to_pyexception(vm),
1562        }
1563    }
1564}
1565
1566#[cfg(all(unix, not(target_os = "redox")))]
1567impl ToPyException for rustpython_host_env::socket::AncillaryPackError {
1568    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1569        match self {
1570            Self::ItemTooLarge => vm.new_os_error("ancillary data item too large"),
1571            Self::TooMuchData => vm.new_os_error("too much ancillary data"),
1572            Self::UnexpectedNullHeader => {
1573                vm.new_runtime_error("unexpected NULL result from CMSG_FIRSTHDR/CMSG_NXTHDR")
1574            }
1575        }
1576    }
1577}
1578
1579#[cfg(any(unix, windows))]
1580impl ToPyException for rustpython_host_env::time::CheckedTmError {
1581    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1582        match self {
1583            Self::YearOutOfRange => vm.new_overflow_error("year out of range"),
1584            Self::MonthOutOfRange => vm.new_value_error("month out of range"),
1585            Self::DayOfMonthOutOfRange => vm.new_value_error("day of month out of range"),
1586            Self::HourOutOfRange => vm.new_value_error("hour out of range"),
1587            Self::MinuteOutOfRange => vm.new_value_error("minute out of range"),
1588            Self::SecondsOutOfRange => vm.new_value_error("seconds out of range"),
1589            Self::DayOfWeekOutOfRange => vm.new_value_error("day of week out of range"),
1590            Self::DayOfYearOutOfRange => vm.new_value_error("day of year out of range"),
1591            Self::EmbeddedNul => vm.new_value_error("embedded null character"),
1592        }
1593    }
1594}
1595
1596#[cfg(windows)]
1597impl ToPyException for rustpython_host_env::winapi::BuildEnvironmentBlockError {
1598    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1599        match self {
1600            Self::ContainsNul => vm.new_value_error("embedded null character"),
1601            Self::IllegalName => vm.new_value_error("illegal environment variable name"),
1602        }
1603    }
1604}
1605
1606#[cfg(windows)]
1607impl ToPyException for rustpython_host_env::winapi::BatchedWaitError {
1608    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1609        match self {
1610            Self::Timeout => vm
1611                .new_os_subtype_error(
1612                    vm.ctx.exceptions.timeout_error.to_owned(),
1613                    None,
1614                    "timed out",
1615                )
1616                .upcast(),
1617            Self::Interrupted => vm
1618                .new_errno_error(libc::EINTR, "Interrupted system call")
1619                .upcast(),
1620            Self::Os(err) => vm.new_os_error(*err as i32),
1621        }
1622    }
1623}
1624
1625#[cfg(windows)]
1626impl ToPyException for rustpython_host_env::nt::ReadlinkError {
1627    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1628        match self {
1629            Self::Io(err) => err.to_pyexception(vm),
1630            Self::NotSymbolicLink => {
1631                vm.new_os_error("The file or directory is not a reparse point")
1632            }
1633            Self::InvalidReparseData => vm.new_os_error("Invalid reparse data"),
1634        }
1635    }
1636}
1637
1638#[cfg(windows)]
1639impl ToPyException for rustpython_host_env::nt::ReadConsoleError {
1640    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1641        match self {
1642            Self::Io(err) => err.to_pyexception(vm),
1643            Self::BufferTooSmall {
1644                available,
1645                required,
1646            } => vm.new_system_error(format!(
1647                "Buffer had room for {available} bytes but {required} bytes required",
1648            )),
1649        }
1650    }
1651}
1652
1653#[cfg(windows)]
1654impl ToPyException for rustpython_host_env::winreg::ExpandEnvironmentStringsError {
1655    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1656        match self {
1657            Self::Os => vm.new_os_error("ExpandEnvironmentStringsW failed"),
1658            Self::Utf16(e) => vm.new_value_error(format!("UTF16 error: {e}")),
1659        }
1660    }
1661}
1662
1663#[cfg(windows)]
1664impl ToPyException for rustpython_host_env::winreg::QueryStringError {
1665    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1666        match self {
1667            Self::Code(err) => std::io::Error::from_raw_os_error(*err as i32).to_pyexception(vm),
1668            Self::Utf16(e) => vm.new_value_error(format!("UTF16 error: {e}")),
1669        }
1670    }
1671}
1672
1673#[cfg(windows)]
1674impl ToPyException for rustpython_host_env::wmi::ExecQueryError {
1675    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1676        match self {
1677            Self::MoreData => vm.new_os_error(format!(
1678                "Query returns more than {} characters",
1679                rustpython_host_env::wmi::BUFFER_SIZE
1680            )),
1681            Self::Code(err) => std::io::Error::from_raw_os_error(*err as i32).to_pyexception(vm),
1682        }
1683    }
1684}
1685
1686#[cfg(unix)]
1687impl ToPyException for rustpython_host_env::multiprocessing::SemError {
1688    fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
1689        let excs = &vm.ctx.exceptions;
1690        let exc_type = match self {
1691            Self::AlreadyExists => excs.file_exists_error.to_owned(),
1692            Self::NotFound => excs.file_not_found_error.to_owned(),
1693            _ => excs.os_error.to_owned(),
1694        };
1695        vm.new_os_subtype_error(exc_type, Some(self.raw_os_error()), self.description())
1696            .upcast()
1697    }
1698}
1699
1700pub(super) mod types {
1701    use crate::class::PyClassDef;
1702    use crate::common::lock::PyRwLock;
1703    use crate::object::{Traverse, TraverseFn};
1704    #[cfg_attr(target_arch = "wasm32", allow(unused_imports))]
1705    use crate::{
1706        AsObject, Py, PyAtomicRef, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
1707        VirtualMachine,
1708        builtins::{
1709            PyInt, PyStrRef, PyTupleRef, PyType, PyTypeRef, traceback::PyTracebackRef,
1710            tuple::IntoPyTuple,
1711        },
1712        convert::ToPyResult,
1713        function::{ArgBytesLike, FuncArgs, KwArgs, PySetterValue},
1714        types::{Constructor, Initializer},
1715    };
1716    use core::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
1717    use crossbeam_utils::atomic::AtomicCell;
1718    use itertools::Itertools;
1719    use rustpython_common::{
1720        str::UnicodeEscapeCodepoint,
1721        wtf8::{Wtf8, Wtf8Buf, wtf8_concat},
1722    };
1723
1724    // Re-export exception group types from dedicated module
1725    pub use crate::exception_group::types::PyBaseExceptionGroup;
1726
1727    // This module is designed to be used as `use builtins::*;`.
1728    // Do not add any pub symbols not included in builtins module.
1729    // `PyBaseExceptionRef` is the only exception.
1730
1731    pub type PyBaseExceptionRef = PyRef<PyBaseException>;
1732
1733    // Sorted By Hierarchy then alphabetized.
1734
1735    #[pyclass(module = false, name = "BaseException", traverse = "manual")]
1736    pub struct PyBaseException {
1737        pub(super) traceback: PyRwLock<Option<PyTracebackRef>>,
1738        pub(super) cause: PyRwLock<Option<PyRef<Self>>>,
1739        pub(super) context: PyRwLock<Option<PyRef<Self>>>,
1740        #[pymember(name = "__suppress_context__", writable)]
1741        pub(super) suppress_context: AtomicBool,
1742        pub(super) args: PyRwLock<PyTupleRef>,
1743    }
1744
1745    #[pyexception(name, base = PyBaseException, ctx = "system_exit", traverse = "manual")]
1746    #[repr(C)]
1747    pub struct PySystemExit {
1748        base: PyBaseException,
1749        #[pymember(writable)]
1750        code: PyAtomicRef<Option<PyObject>>,
1751    }
1752
1753    impl crate::class::PySubclass for PySystemExit {
1754        type Base = PyBaseException;
1755        fn as_base(&self) -> &Self::Base {
1756            &self.base
1757        }
1758    }
1759
1760    unsafe impl Traverse for PySystemExit {
1761        fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
1762            self.base.traverse(tracer_fn);
1763            if let Some(obj) = self.code.deref() {
1764                tracer_fn(obj);
1765            }
1766        }
1767    }
1768
1769    impl core::fmt::Debug for PySystemExit {
1770        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1771            f.debug_struct("PySystemExit").finish_non_exhaustive()
1772        }
1773    }
1774
1775    #[pyexception(with(Constructor, Initializer))]
1776    impl PySystemExit {}
1777
1778    impl Initializer for PySystemExit {
1779        type Args = FuncArgs;
1780        fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
1781            // Call BaseException_init first (handles args)
1782            let code = match args.args.len() {
1783                0 => vm.ctx.none(),
1784                1 => args.args[0].clone(),
1785                _ => vm.ctx.new_tuple(args.args.clone()).into(),
1786            };
1787            PyBaseException::slot_init(zelf, args, vm)?;
1788            let exc: &Py<Self> = zelf.downcast_ref::<Self>().unwrap();
1789            exc.code.swap_to_temporary_refs(Some(code), vm);
1790            Ok(())
1791        }
1792
1793        fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
1794            unreachable!("slot_init is defined")
1795        }
1796    }
1797
1798    impl Constructor for PySystemExit {
1799        type Args = FuncArgs;
1800
1801        fn py_new(_cls: &Py<PyType>, args: FuncArgs, vm: &VirtualMachine) -> PyResult<Self> {
1802            let base_exception = PyBaseException::new(args.args, vm);
1803            Ok(Self {
1804                base: base_exception,
1805                code: None.into(),
1806            })
1807        }
1808    }
1809
1810    #[pyexception(name, base = PyBaseException, ctx = "generator_exit", impl)]
1811    #[derive(Debug)]
1812    #[repr(transparent)]
1813    pub struct PyGeneratorExit(PyBaseException);
1814
1815    #[pyexception(name, base = PyBaseException, ctx = "keyboard_interrupt", impl)]
1816    #[derive(Debug)]
1817    #[repr(transparent)]
1818    pub struct PyKeyboardInterrupt(PyBaseException);
1819
1820    #[pyexception(name, base = PyBaseException, ctx = "exception_type", impl)]
1821    #[derive(Debug)]
1822    #[repr(transparent)]
1823    pub struct PyException(PyBaseException);
1824
1825    #[pyexception(name, base = PyException, ctx = "stop_iteration", traverse = "manual")]
1826    #[repr(C)]
1827    pub struct PyStopIteration {
1828        base: PyException,
1829        #[pymember(writable)]
1830        value: PyAtomicRef<Option<PyObject>>,
1831    }
1832
1833    impl crate::class::PySubclass for PyStopIteration {
1834        type Base = PyException;
1835        fn as_base(&self) -> &Self::Base {
1836            &self.base
1837        }
1838    }
1839
1840    impl core::fmt::Debug for PyStopIteration {
1841        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1842            f.debug_struct("PyStopIteration").finish_non_exhaustive()
1843        }
1844    }
1845
1846    unsafe impl Traverse for PyStopIteration {
1847        fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
1848            self.base.0.traverse(tracer_fn);
1849            if let Some(obj) = self.value.deref() {
1850                tracer_fn(obj);
1851            }
1852        }
1853    }
1854
1855    impl Constructor for PyStopIteration {
1856        type Args = FuncArgs;
1857
1858        fn py_new(_cls: &Py<PyType>, args: FuncArgs, vm: &VirtualMachine) -> PyResult<Self> {
1859            let base_exception = PyBaseException::new(args.args, vm);
1860            Ok(Self {
1861                base: PyException(base_exception),
1862                value: None.into(),
1863            })
1864        }
1865    }
1866
1867    impl Initializer for PyStopIteration {
1868        type Args = FuncArgs;
1869        fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
1870            let value = match args.args.len() {
1871                0 => vm.ctx.none(),
1872                _ => args.args[0].clone(),
1873            };
1874            PyBaseException::slot_init(zelf, args, vm)?;
1875            let exc: &Py<Self> = zelf.downcast_ref::<Self>().unwrap();
1876            exc.value.swap_to_temporary_refs(Some(value), vm);
1877            Ok(())
1878        }
1879
1880        fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
1881            unreachable!("slot_init is defined")
1882        }
1883    }
1884
1885    #[pyexception(with(Constructor, Initializer))]
1886    impl PyStopIteration {}
1887
1888    #[pyexception(name, base = PyException, ctx = "stop_async_iteration", impl)]
1889    #[derive(Debug)]
1890    #[repr(transparent)]
1891    pub struct PyStopAsyncIteration(PyException);
1892
1893    #[pyexception(name, base = PyException, ctx = "arithmetic_error", impl)]
1894    #[derive(Debug)]
1895    #[repr(transparent)]
1896    pub struct PyArithmeticError(PyException);
1897
1898    #[pyexception(name, base = PyArithmeticError, ctx = "floating_point_error", impl)]
1899    #[derive(Debug)]
1900    #[repr(transparent)]
1901    pub struct PyFloatingPointError(PyArithmeticError);
1902
1903    #[pyexception(name, base = PyArithmeticError, ctx = "overflow_error", impl)]
1904    #[derive(Debug)]
1905    #[repr(transparent)]
1906    pub struct PyOverflowError(PyArithmeticError);
1907
1908    #[pyexception(name, base = PyArithmeticError, ctx = "zero_division_error", impl)]
1909    #[derive(Debug)]
1910    #[repr(transparent)]
1911    pub struct PyZeroDivisionError(PyArithmeticError);
1912
1913    #[pyexception(name, base = PyException, ctx = "assertion_error", impl)]
1914    #[derive(Debug)]
1915    #[repr(transparent)]
1916    pub struct PyAssertionError(PyException);
1917
1918    #[pyexception(name, base = PyException, ctx = "attribute_error", traverse = "manual")]
1919    #[repr(C)]
1920    pub struct PyAttributeError {
1921        base: PyException,
1922        #[pymember(writable)]
1923        name: PyAtomicRef<Option<PyObject>>,
1924        #[pymember(writable)]
1925        obj: PyAtomicRef<Option<PyObject>>,
1926    }
1927
1928    impl crate::class::PySubclass for PyAttributeError {
1929        type Base = PyException;
1930        fn as_base(&self) -> &Self::Base {
1931            &self.base
1932        }
1933    }
1934
1935    impl core::fmt::Debug for PyAttributeError {
1936        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1937            f.debug_struct("PyAttributeError").finish_non_exhaustive()
1938        }
1939    }
1940
1941    unsafe impl Traverse for PyAttributeError {
1942        fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
1943            self.base.0.traverse(tracer_fn);
1944            if let Some(obj) = self.name.deref() {
1945                tracer_fn(obj);
1946            }
1947            if let Some(obj) = self.obj.deref() {
1948                tracer_fn(obj);
1949            }
1950        }
1951    }
1952
1953    impl Constructor for PyAttributeError {
1954        type Args = FuncArgs;
1955
1956        fn py_new(_cls: &Py<PyType>, args: FuncArgs, vm: &VirtualMachine) -> PyResult<Self> {
1957            Ok(Self {
1958                base: PyException(PyBaseException::new(args.args, vm)),
1959                name: None.into(),
1960                obj: None.into(),
1961            })
1962        }
1963    }
1964
1965    #[pyexception(with(Constructor, Initializer))]
1966    impl PyAttributeError {
1967        #[pymethod]
1968        fn __getstate__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
1969            let obj = zelf.as_object();
1970            let state = match obj.dict() {
1971                Some(dict) => dict.copy().into_ref(&vm.ctx),
1972                None => vm.ctx.new_dict(),
1973            };
1974            let _ = state.del_item("obj", vm);
1975            if let Some(name) = zelf.name.load_owned() {
1976                state.set_item("name", name, vm)?;
1977            }
1978            Ok(state.into())
1979        }
1980
1981        #[pymethod]
1982        fn __reduce__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
1983            let state = Self::__getstate__(zelf, vm)?;
1984            let base: &Py<PyBaseException> = zelf
1985                .as_object()
1986                .downcast_ref()
1987                .expect("AttributeError is a BaseException");
1988            Ok(vm.ctx.new_tuple(vec![
1989                zelf.class().to_owned().into(),
1990                base.args().into(),
1991                state,
1992            ]))
1993        }
1994    }
1995
1996    impl Initializer for PyAttributeError {
1997        type Args = FuncArgs;
1998
1999        fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
2000            // Only 'name' and 'obj' kwargs are allowed
2001            let mut kwargs = args.kwargs.clone();
2002            let name = kwargs.swap_remove("name");
2003            let obj = kwargs.swap_remove("obj");
2004
2005            // Reject unknown kwargs
2006            if let Some(invalid_key) = kwargs.keys().next() {
2007                return Err(vm.new_unexpected_keyword_type_error(
2008                    Some(Self::NAME),
2009                    &invalid_key.to_string(),
2010                ));
2011            }
2012
2013            // Pass args without kwargs to BaseException_init
2014            let base_args = FuncArgs::new(args.args, KwArgs::default());
2015            PyBaseException::slot_init(zelf, base_args, vm)?;
2016
2017            let exc: &Py<Self> = zelf
2018                .downcast_ref()
2019                .expect("AttributeError instance has AttributeError payload");
2020            exc.name.swap_to_temporary_refs(name, vm);
2021            exc.obj.swap_to_temporary_refs(obj, vm);
2022            Ok(())
2023        }
2024
2025        fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
2026            unreachable!("slot_init is defined")
2027        }
2028    }
2029
2030    #[pyexception(name, base = PyException, ctx = "buffer_error", impl)]
2031    #[derive(Debug)]
2032    #[repr(transparent)]
2033    pub struct PyBufferError(PyException);
2034
2035    #[pyexception(name, base = PyException, ctx = "eof_error", impl)]
2036    #[derive(Debug)]
2037    #[repr(transparent)]
2038    pub struct PyEOFError(PyException);
2039
2040    #[pyexception(name, base = PyException, ctx = "import_error", traverse = "manual")]
2041    #[repr(C)]
2042    pub struct PyImportError {
2043        base: PyException,
2044        #[pymember(writable)]
2045        msg: PyAtomicRef<Option<PyObject>>,
2046        #[pymember(writable)]
2047        name: PyAtomicRef<Option<PyObject>>,
2048        #[pymember(writable)]
2049        path: PyAtomicRef<Option<PyObject>>,
2050        #[pymember(writable)]
2051        name_from: PyAtomicRef<Option<PyObject>>,
2052    }
2053
2054    impl crate::class::PySubclass for PyImportError {
2055        type Base = PyException;
2056        fn as_base(&self) -> &Self::Base {
2057            &self.base
2058        }
2059    }
2060
2061    impl core::fmt::Debug for PyImportError {
2062        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2063            f.debug_struct("PyImportError").finish_non_exhaustive()
2064        }
2065    }
2066
2067    unsafe impl Traverse for PyImportError {
2068        fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
2069            self.base.0.traverse(tracer_fn);
2070            if let Some(obj) = self.msg.deref() {
2071                tracer_fn(obj);
2072            }
2073            if let Some(obj) = self.name.deref() {
2074                tracer_fn(obj);
2075            }
2076            if let Some(obj) = self.path.deref() {
2077                tracer_fn(obj);
2078            }
2079            if let Some(obj) = self.name_from.deref() {
2080                tracer_fn(obj);
2081            }
2082        }
2083    }
2084
2085    impl Constructor for PyImportError {
2086        type Args = FuncArgs;
2087
2088        fn py_new(_cls: &Py<PyType>, args: FuncArgs, vm: &VirtualMachine) -> PyResult<Self> {
2089            Ok(Self {
2090                base: PyException(PyBaseException::new(args.args, vm)),
2091                msg: None.into(),
2092                name: None.into(),
2093                path: None.into(),
2094                name_from: None.into(),
2095            })
2096        }
2097    }
2098
2099    #[pyexception(with(Constructor, Initializer))]
2100    impl PyImportError {
2101        #[pyslot]
2102        fn slot_str(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
2103            let exc: &Py<Self> = zelf
2104                .downcast_ref()
2105                .expect("slot wrapper checked ImportError");
2106            if let Some(msg) = exc.msg.load_owned()
2107                && msg.class().is(vm.ctx.types.str_type)
2108            {
2109                return msg.str(vm);
2110            }
2111            let base: &Py<PyBaseException> =
2112                zelf.downcast_ref().expect("ImportError is a BaseException");
2113            Ok(base.__str__(vm))
2114        }
2115
2116        #[pymethod]
2117        fn __reduce__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
2118            let obj = zelf.as_object();
2119            let exc = zelf;
2120            let base: &Py<PyBaseException> =
2121                obj.downcast_ref().expect("ImportError is a BaseException");
2122            let args: PyObjectRef = match base.get_arg(0) {
2123                Some(arg) => vm.new_tuple((arg,)).into(),
2124                None => base.args().into(),
2125            };
2126            let mut result: Vec<PyObjectRef> = vec![obj.class().to_owned().into(), args];
2127
2128            let mut state = obj
2129                .dict()
2130                .filter(|dict| !dict.is_empty())
2131                .map(|dict| dict.copy().into_ref(&vm.ctx));
2132            let mut insert = |key: &str, value: Option<PyObjectRef>| -> PyResult<()> {
2133                let Some(value) = value else {
2134                    return Ok(());
2135                };
2136                let dict = state.get_or_insert_with(|| vm.ctx.new_dict());
2137                dict.set_item(key, value, vm)?;
2138                Ok(())
2139            };
2140            insert("name", exc.name.load_owned())?;
2141            insert("path", exc.path.load_owned())?;
2142            insert("name_from", exc.name_from.load_owned())?;
2143            if let Some(state) = state {
2144                result.push(state.into());
2145            }
2146            Ok(result.into_pytuple(vm))
2147        }
2148    }
2149
2150    impl Initializer for PyImportError {
2151        type Args = FuncArgs;
2152
2153        fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
2154            // Only 'name', 'path', 'name_from' kwargs are allowed
2155            let mut kwargs = args.kwargs.clone();
2156            let name = kwargs.swap_remove("name");
2157            let path = kwargs.swap_remove("path");
2158            let name_from = kwargs.swap_remove("name_from");
2159
2160            // Check for any remaining invalid keyword arguments
2161            if let Some(invalid_key) = kwargs.keys().next() {
2162                return Err(vm.new_unexpected_keyword_type_error(
2163                    Some("ImportError"),
2164                    &invalid_key.to_string(),
2165                ));
2166            }
2167
2168            let msg = if args.args.len() == 1 {
2169                Some(args.args[0].clone())
2170            } else {
2171                None
2172            };
2173            let base_args = FuncArgs::new(args.args, KwArgs::default());
2174            PyBaseException::slot_init(zelf, base_args, vm)?;
2175            let exc: &Py<Self> = zelf
2176                .downcast_ref()
2177                .expect("ImportError instance has ImportError payload");
2178            exc.name.swap_to_temporary_refs(name, vm);
2179            exc.path.swap_to_temporary_refs(path, vm);
2180            exc.name_from.swap_to_temporary_refs(name_from, vm);
2181            exc.msg.swap_to_temporary_refs(msg, vm);
2182            Ok(())
2183        }
2184
2185        fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
2186            unreachable!("slot_init is defined")
2187        }
2188    }
2189
2190    #[pyexception(name, base = PyImportError, ctx = "module_not_found_error", impl)]
2191    #[derive(Debug)]
2192    #[repr(transparent)]
2193    pub struct PyModuleNotFoundError(PyImportError);
2194
2195    #[pyexception(name, base = PyException, ctx = "lookup_error", impl)]
2196    #[derive(Debug)]
2197    #[repr(transparent)]
2198    pub struct PyLookupError(PyException);
2199
2200    #[pyexception(name, base = PyLookupError, ctx = "index_error", impl)]
2201    #[derive(Debug)]
2202    #[repr(transparent)]
2203    pub struct PyIndexError(PyLookupError);
2204
2205    #[pyexception(name, base = PyLookupError, ctx = "key_error")]
2206    #[derive(Debug)]
2207    #[repr(transparent)]
2208    pub struct PyKeyError(PyLookupError);
2209
2210    #[pyexception]
2211    impl PyKeyError {
2212        #[pymethod]
2213        fn __str__(zelf: &Py<PyBaseException>, vm: &VirtualMachine) -> PyStrRef {
2214            let args = zelf.args();
2215            if args.as_slice().len() == 1 {
2216                vm.exception_args_as_string(&args, false)
2217                    .into_iter()
2218                    .exactly_one()
2219                    .unwrap()
2220            } else {
2221                zelf.__str__(vm)
2222            }
2223        }
2224    }
2225
2226    #[pyexception(name, base = PyException, ctx = "memory_error", impl, payload = "manual", traverse = "manual")]
2227    #[derive(Debug)]
2228    #[repr(transparent)]
2229    pub struct PyMemoryError(PyException);
2230
2231    impl PyMemoryError {
2232        pub(crate) fn empty(vm: &VirtualMachine) -> Self {
2233            Self(PyException(PyBaseException::new(vec![], vm)))
2234        }
2235    }
2236
2237    unsafe impl Traverse for PyMemoryError {
2238        fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
2239            self.0.0.traverse(tracer_fn);
2240        }
2241
2242        fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
2243            let base = &mut self.0.0;
2244            if let Some(traceback) = base.traceback.get_mut().take() {
2245                out.push(traceback.into());
2246            }
2247
2248            if let Some(cause) = base.cause.get_mut().take() {
2249                out.push(cause.into());
2250            }
2251
2252            if let Some(context) = base.context.get_mut().take() {
2253                out.push(context.into());
2254            }
2255        }
2256    }
2257
2258    #[pyexception(name, base = PyException, ctx = "name_error", traverse = "manual")]
2259    #[repr(C)]
2260    pub struct PyNameError {
2261        base: PyException,
2262        #[pymember(writable)]
2263        name: PyAtomicRef<Option<PyObject>>,
2264    }
2265
2266    impl crate::class::PySubclass for PyNameError {
2267        type Base = PyException;
2268        fn as_base(&self) -> &Self::Base {
2269            &self.base
2270        }
2271    }
2272
2273    impl core::fmt::Debug for PyNameError {
2274        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2275            f.debug_struct("PyNameError").finish_non_exhaustive()
2276        }
2277    }
2278
2279    unsafe impl Traverse for PyNameError {
2280        fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
2281            self.base.0.traverse(tracer_fn);
2282            if let Some(obj) = self.name.deref() {
2283                tracer_fn(obj);
2284            }
2285        }
2286    }
2287
2288    impl Constructor for PyNameError {
2289        type Args = FuncArgs;
2290
2291        fn py_new(_cls: &Py<PyType>, args: FuncArgs, vm: &VirtualMachine) -> PyResult<Self> {
2292            Ok(Self {
2293                base: PyException(PyBaseException::new(args.args, vm)),
2294                name: None.into(),
2295            })
2296        }
2297    }
2298
2299    // NameError_init: handles the .name kwarg
2300    #[pyexception(with(Constructor, Initializer))]
2301    impl PyNameError {}
2302
2303    impl Initializer for PyNameError {
2304        type Args = FuncArgs;
2305        fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
2306            // Only 'name' kwarg is allowed
2307            let mut kwargs = args.kwargs.clone();
2308            let name = kwargs.swap_remove("name");
2309
2310            // Reject unknown kwargs
2311            if let Some(invalid_key) = kwargs.keys().next() {
2312                return Err(vm.new_unexpected_keyword_type_error(
2313                    Some(Self::NAME),
2314                    &invalid_key.to_string(),
2315                ));
2316            }
2317
2318            // Pass args without kwargs to BaseException_init
2319            let base_args = FuncArgs::new(args.args, KwArgs::default());
2320            PyBaseException::slot_init(zelf, base_args, vm)?;
2321
2322            let exc: &Py<Self> = zelf
2323                .downcast_ref()
2324                .expect("NameError instance has NameError payload");
2325            // Absent keyword clears the field. Re-init must not keep the old name.
2326            exc.name.swap_to_temporary_refs(name, vm);
2327            Ok(())
2328        }
2329
2330        fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
2331            unreachable!("slot_init is defined")
2332        }
2333    }
2334
2335    #[pyexception(name, base = PyNameError, ctx = "unbound_local_error", impl)]
2336    #[derive(Debug)]
2337    #[repr(transparent)]
2338    pub struct PyUnboundLocalError(PyNameError);
2339
2340    #[pyexception(name, base = PyException, ctx = "os_error", traverse = "manual")]
2341    #[repr(C)]
2342    pub struct PyOSError {
2343        base: PyException,
2344        #[pymember(writable)]
2345        errno: PyAtomicRef<Option<PyObject>>,
2346        #[pymember(writable)]
2347        strerror: PyAtomicRef<Option<PyObject>>,
2348        #[pymember(writable)]
2349        filename: PyAtomicRef<Option<PyObject>>,
2350        #[pymember(writable)]
2351        filename2: PyAtomicRef<Option<PyObject>>,
2352        #[cfg(windows)]
2353        #[pymember(writable)]
2354        winerror: PyAtomicRef<Option<PyObject>>,
2355        // For BlockingIOError: characters written before blocking occurred
2356        // -1 means not set (AttributeError when accessed)
2357        written: AtomicCell<isize>,
2358    }
2359
2360    impl crate::class::PySubclass for PyOSError {
2361        type Base = PyException;
2362        fn as_base(&self) -> &Self::Base {
2363            &self.base
2364        }
2365    }
2366
2367    impl core::fmt::Debug for PyOSError {
2368        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2369            f.debug_struct("PyOSError").finish_non_exhaustive()
2370        }
2371    }
2372
2373    unsafe impl Traverse for PyOSError {
2374        fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
2375            // `self.base` is a `PyException` newtype whose `MaybeTraverse` is a
2376            // no-op; reach the underlying `PyBaseException` so its traceback,
2377            // cause, context and args are visited by the collector.
2378            self.base.0.traverse(tracer_fn);
2379            if let Some(obj) = self.errno.deref() {
2380                tracer_fn(obj);
2381            }
2382            if let Some(obj) = self.strerror.deref() {
2383                tracer_fn(obj);
2384            }
2385            if let Some(obj) = self.filename.deref() {
2386                tracer_fn(obj);
2387            }
2388            if let Some(obj) = self.filename2.deref() {
2389                tracer_fn(obj);
2390            }
2391            #[cfg(windows)]
2392            if let Some(obj) = self.winerror.deref() {
2393                tracer_fn(obj);
2394            }
2395        }
2396    }
2397
2398    // OS Errors:
2399    impl Constructor for PyOSError {
2400        type Args = FuncArgs;
2401
2402        fn py_new(_cls: &Py<PyType>, args: FuncArgs, vm: &VirtualMachine) -> PyResult<Self> {
2403            let len = args.args.len();
2404            // CPython only sets errno/strerror when args len is 2-5
2405            let (errno, strerror) = if (2..=5).contains(&len) {
2406                (Some(args.args[0].clone()), Some(args.args[1].clone()))
2407            } else {
2408                (None, None)
2409            };
2410            let filename = if (3..=5).contains(&len) {
2411                Some(args.args[2].clone())
2412            } else {
2413                None
2414            };
2415            let filename2 = if len == 5 {
2416                args.args.get(4).cloned()
2417            } else {
2418                None
2419            };
2420            // Truncate args for base exception when 3-5 args
2421            let base_args = if (3..=5).contains(&len) {
2422                args.args[..2].to_vec()
2423            } else {
2424                args.args.to_vec()
2425            };
2426            let base_exception = PyBaseException::new(base_args, vm);
2427            Ok(Self {
2428                base: PyException(base_exception),
2429                errno: errno.into(),
2430                strerror: strerror.into(),
2431                filename: filename.into(),
2432                filename2: filename2.into(),
2433                #[cfg(windows)]
2434                winerror: None.into(),
2435                written: AtomicCell::new(-1),
2436            })
2437        }
2438
2439        fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
2440            if oserror_use_init(&cls) {
2441                let payload = Self {
2442                    base: PyException(PyBaseException::new(vec![], vm)),
2443                    errno: None.into(),
2444                    strerror: None.into(),
2445                    filename: None.into(),
2446                    filename2: None.into(),
2447                    #[cfg(windows)]
2448                    winerror: None.into(),
2449                    written: AtomicCell::new(-1),
2450                };
2451                return payload
2452                    .into_ref_with_type_lazy_dict(vm, cls)
2453                    .map(Into::into);
2454            }
2455
2456            if !args.kwargs.is_empty() {
2457                return Err(
2458                    vm.new_type_error(format!("{}() takes no keyword arguments", cls.slot_name()))
2459                );
2460            }
2461
2462            // We need this method, because of how `CPython` copies `init`
2463            // from `BaseException` in `SimpleExtendsException` macro.
2464            // See: `BaseException_new`
2465            if *cls.name() == *vm.ctx.exceptions.os_error.name() {
2466                let args_vec = args.args.to_vec();
2467                let len = args_vec.len();
2468                if (2..=5).contains(&len) {
2469                    let errno = &args_vec[0];
2470                    if let Some(error) = errno
2471                        .downcast_ref::<PyInt>()
2472                        .and_then(|errno| errno.try_to_primitive::<i32>(vm).ok())
2473                        .and_then(|errno| super::errno_to_exc_type(errno, vm))
2474                        .and_then(|typ| {
2475                            vm.new_payload_exception::<Self>(typ.to_owned(), args_vec.into())
2476                                .ok()
2477                        })
2478                    {
2479                        return error.to_pyresult(vm);
2480                    }
2481                }
2482            }
2483            let payload = Self::py_new(&cls, args.clone(), vm)?;
2484            let obj = payload.into_ref_with_type_lazy_dict(vm, cls)?;
2485            oserror_init(obj.as_object(), args, vm)?;
2486            Ok(obj.into())
2487        }
2488    }
2489
2490    fn oserror_use_init(cls: &Py<PyType>) -> bool {
2491        let init = cls.slots.init.load();
2492        let new = cls.slots.new.load();
2493        let slot_init: fn(&PyObject, FuncArgs, &VirtualMachine) -> PyResult<()> =
2494            PyOSError::slot_init;
2495        let slot_new: fn(PyTypeRef, FuncArgs, &VirtualMachine) -> PyResult = PyOSError::slot_new;
2496        !matches!(init, Some(f) if core::ptr::fn_addr_eq(f, slot_init))
2497            && matches!(new, Some(f) if core::ptr::fn_addr_eq(f, slot_new))
2498    }
2499
2500    fn oserror_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
2501        let len = args.args.len();
2502        let mut new_args = args;
2503
2504        // All OSError subclasses use #[repr(transparent)] wrapping PyOSError,
2505        // so we can safely access the PyOSError fields through pointer cast
2506        // SAFETY: All OSError subclasses (FileNotFoundError, etc.) are
2507        // #[repr(transparent)] wrappers around PyOSError with identical memory layout
2508        #[allow(deprecated)]
2509        let exc: &Py<PyOSError> = zelf.downcast_ref::<PyOSError>().unwrap();
2510
2511        // Check if this is BlockingIOError - need to handle characters_written
2512        let is_blocking_io_error = zelf
2513            .class()
2514            .is(vm.ctx.exceptions.blocking_io_error.as_ref());
2515
2516        // SAFETY: slot_init is called during object initialization,
2517        // so fields are None and swap result can be safely ignored
2518        let mut set_filename = true;
2519        if len <= 5 {
2520            // Only set errno/strerror when args len is 2-5
2521            if 2 <= len {
2522                let _ = unsafe { exc.errno.swap(Some(new_args.args[0].clone())) };
2523                let _ = unsafe { exc.strerror.swap(Some(new_args.args[1].clone())) };
2524            }
2525            if 3 <= len {
2526                let third_arg = &new_args.args[2];
2527                // BlockingIOError's 3rd argument can be the number of characters written
2528                if is_blocking_io_error
2529                    && !vm.is_none(third_arg)
2530                    && crate::protocol::PyNumber::check(third_arg)
2531                    && let Ok(written) = third_arg.try_index(vm)
2532                    && let Ok(n) = written.try_to_primitive::<isize>(vm)
2533                {
2534                    exc.written.store(n);
2535                    set_filename = false;
2536                    // The count leaves neither filename taken, so both are
2537                    // put back the way `py_new` found them.
2538                    let _ = unsafe { exc.filename.swap(None) };
2539                    let _ = unsafe { exc.filename2.swap(None) };
2540                }
2541                if set_filename {
2542                    let _ = unsafe { exc.filename.swap(Some(third_arg.clone())) };
2543                }
2544            }
2545            #[cfg(windows)]
2546            if 4 <= len {
2547                let winerror = new_args.args.get(3).cloned();
2548                // Store original winerror
2549                let _ = unsafe { exc.winerror.swap(winerror.clone()) };
2550
2551                // Convert winerror to errno and update errno + args[0]
2552                if let Some(errno) = winerror
2553                    .as_ref()
2554                    .and_then(|w| w.downcast_ref::<crate::builtins::PyInt>())
2555                    .and_then(|w| w.try_to_primitive::<i32>(vm).ok())
2556                    .map(crate::host_env::os::winerror_to_errno)
2557                {
2558                    let errno_obj = vm.new_pyobj(errno);
2559                    let _ = unsafe { exc.errno.swap(Some(errno_obj.clone())) };
2560                    new_args.args[0] = errno_obj;
2561                }
2562            }
2563        }
2564
2565        // A second filename, and the two arguments the rest are cut back
2566        // to, both follow the filename itself having been taken.
2567        let has_filename = exc
2568            .filename
2569            .load_owned()
2570            .as_ref()
2571            .is_some_and(|f| !vm.is_none(f));
2572        if (3..=5).contains(&len) && has_filename {
2573            if let Some(filename2) = new_args.args.get(4)
2574                && !vm.is_none(filename2)
2575            {
2576                let _ = unsafe { exc.filename2.swap(Some(filename2.clone())) };
2577            }
2578            new_args.args.truncate(2);
2579        }
2580        PyBaseException::slot_init(zelf, new_args, vm)
2581    }
2582
2583    impl Initializer for PyOSError {
2584        type Args = FuncArgs;
2585
2586        fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
2587            if !oserror_use_init(zelf.class()) {
2588                return Ok(());
2589            }
2590            if !args.kwargs.is_empty() {
2591                return Err(vm.new_type_error(format!(
2592                    "{}() takes no keyword arguments",
2593                    zelf.class().slot_name()
2594                )));
2595            }
2596            oserror_init(zelf, args, vm)
2597        }
2598
2599        fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
2600            unreachable!("slot_init is defined")
2601        }
2602    }
2603
2604    #[pyexception(with(Constructor, Initializer))]
2605    impl PyOSError {
2606        #[pyslot]
2607        fn slot_str(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
2608            let obj = zelf;
2609
2610            // Get OSError fields directly
2611            let errno_field = obj.get_attr("errno", vm).ok().filter(|v| !vm.is_none(v));
2612            let strerror = obj.get_attr("strerror", vm).ok().filter(|v| !vm.is_none(v));
2613            let filename = obj.get_attr("filename", vm).ok().filter(|v| !vm.is_none(v));
2614            let filename2 = obj
2615                .get_attr("filename2", vm)
2616                .ok()
2617                .filter(|v| !vm.is_none(v));
2618            #[cfg(windows)]
2619            let winerror = obj.get_attr("winerror", vm).ok().filter(|v| !vm.is_none(v));
2620
2621            // Windows: winerror takes priority over errno
2622            #[cfg(windows)]
2623            if let Some(ref win_err) = winerror {
2624                let code = win_err.str(vm)?;
2625                if let Some(ref f) = filename {
2626                    let msg = strerror
2627                        .as_ref()
2628                        .map(|s| s.str(vm))
2629                        .transpose()?
2630                        .map_or_else(|| "None".to_owned(), |s| s.to_string());
2631
2632                    if let Some(ref f2) = filename2 {
2633                        return Ok(vm.ctx.new_str(format!(
2634                            "[WinError {}] {}: {} -> {}",
2635                            code,
2636                            msg,
2637                            f.repr(vm)?,
2638                            f2.repr(vm)?
2639                        )));
2640                    }
2641                    return Ok(vm.ctx.new_str(format!(
2642                        "[WinError {}] {}: {}",
2643                        code,
2644                        msg,
2645                        f.repr(vm)?
2646                    )));
2647                }
2648                // winerror && strerror (no filename)
2649                if let Some(ref s) = strerror {
2650                    return Ok(vm
2651                        .ctx
2652                        .new_str(format!("[WinError {}] {}", code, s.str(vm)?)));
2653                }
2654            }
2655
2656            // Non-Windows or fallback: use errno
2657            if let Some(ref f) = filename {
2658                let errno_str = errno_field
2659                    .as_ref()
2660                    .map(|e| e.str(vm))
2661                    .transpose()?
2662                    .map_or_else(|| "None".to_owned(), |s| s.to_string());
2663                let msg = strerror
2664                    .as_ref()
2665                    .map(|s| s.str(vm))
2666                    .transpose()?
2667                    .map_or_else(|| "None".to_owned(), |s| s.to_string());
2668                if let Some(ref f2) = filename2 {
2669                    return Ok(vm.ctx.new_str(format!(
2670                        "[Errno {}] {}: {} -> {}",
2671                        errno_str,
2672                        msg,
2673                        f.repr(vm)?,
2674                        f2.repr(vm)?
2675                    )));
2676                }
2677                return Ok(vm.ctx.new_str(format!(
2678                    "[Errno {}] {}: {}",
2679                    errno_str,
2680                    msg,
2681                    f.repr(vm)?
2682                )));
2683            }
2684
2685            // errno && strerror (no filename)
2686            if let (Some(e), Some(s)) = (&errno_field, &strerror) {
2687                return Ok(vm
2688                    .ctx
2689                    .new_str(format!("[Errno {}] {}", e.str(vm)?, s.str(vm)?)));
2690            }
2691
2692            // fallback to BaseException.__str__
2693            let base: &Py<PyBaseException> =
2694                zelf.downcast_ref().expect("OSError is a BaseException");
2695            Ok(base.__str__(vm))
2696        }
2697
2698        #[pymethod]
2699        fn __reduce__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyTupleRef {
2700            let base: &Py<PyBaseException> = zelf
2701                .as_object()
2702                .downcast_ref()
2703                .expect("OSError is a BaseException");
2704            let args = base.args();
2705            let obj = zelf.as_object().to_owned();
2706            let mut result: Vec<PyObjectRef> = vec![obj.class().to_owned().into()];
2707
2708            if args.as_slice().len() >= 2 && args.as_slice().len() <= 5 {
2709                // SAFETY: len() == 2 is checked so get_arg 1 or 2 won't panic
2710                let errno = base.get_arg(0).expect("args len checked");
2711                let msg = base.get_arg(1).expect("args len checked");
2712
2713                if let Ok(filename) = obj.get_attr("filename", vm) {
2714                    if !vm.is_none(&filename) {
2715                        let mut args_reduced: Vec<PyObjectRef> = vec![errno, msg, filename];
2716
2717                        let filename2 = obj
2718                            .get_attr("filename2", vm)
2719                            .ok()
2720                            .filter(|f| !vm.is_none(f));
2721
2722                        let winerror: Option<PyObjectRef> = cfg_select! {
2723                            windows => obj.get_attr("winerror", vm).ok().filter(|w| !vm.is_none(w)),
2724                            _ => None,
2725                        };
2726
2727                        if let Some(filename2) = filename2 {
2728                            #[allow(clippy::unnecessary_literal_unwrap)]
2729                            let winerror = winerror.unwrap_or_else(|| vm.ctx.none());
2730                            args_reduced.extend([winerror, filename2]);
2731                        } else {
2732                            // Diverges from CPython: include winerror even without
2733                            // filename2 so it survives pickle round-trips.
2734                            if let Some(winerror) = winerror {
2735                                args_reduced.push(winerror);
2736                            }
2737                        }
2738                        result.push(args_reduced.into_pytuple(vm).into());
2739                    } else {
2740                        // filename is None - use original args as-is
2741                        // (may contain winerror at position 3)
2742                        result.push(args.into());
2743                    }
2744                } else {
2745                    result.push(args.into());
2746                }
2747            } else {
2748                result.push(args.into());
2749            }
2750
2751            if let Some(dict) = obj.dict().filter(|x| !x.is_empty()) {
2752                result.push(dict.into());
2753            }
2754            result.into_pytuple(vm)
2755        }
2756
2757        #[pygetset]
2758        fn characters_written(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<isize> {
2759            let written = zelf.written.load();
2760            if written == -1 {
2761                Err(vm.new_attribute_error("characters_written"))
2762            } else {
2763                Ok(written)
2764            }
2765        }
2766
2767        #[pygetset(setter)]
2768        fn set_characters_written(
2769            zelf: &Py<Self>,
2770            value: PySetterValue,
2771            vm: &VirtualMachine,
2772        ) -> PyResult<()> {
2773            match value {
2774                PySetterValue::Delete => {
2775                    if zelf.written.load() == -1 {
2776                        Err(vm.new_attribute_error("characters_written"))
2777                    } else {
2778                        zelf.written.store(-1);
2779                        Ok(())
2780                    }
2781                }
2782                PySetterValue::Assign(v) => {
2783                    let n = v
2784                        .try_index(vm)?
2785                        .try_to_primitive::<isize>(vm)
2786                        .map_err(|_| {
2787                            vm.new_value_error("cannot convert characters_written value to isize")
2788                        })?;
2789                    zelf.written.store(n);
2790                    Ok(())
2791                }
2792            }
2793        }
2794    }
2795
2796    #[pyexception(name, base = PyOSError, ctx = "blocking_io_error", impl)]
2797    #[repr(transparent)]
2798    #[derive(Debug)]
2799    pub struct PyBlockingIOError(PyOSError);
2800
2801    #[pyexception(name, base = PyOSError, ctx = "child_process_error", impl)]
2802    #[repr(transparent)]
2803    #[derive(Debug)]
2804    pub struct PyChildProcessError(PyOSError);
2805
2806    #[pyexception(name, base = PyOSError, ctx = "connection_error", impl)]
2807    #[repr(transparent)]
2808    #[derive(Debug)]
2809    pub struct PyConnectionError(PyOSError);
2810
2811    #[pyexception(name, base = PyConnectionError, ctx = "broken_pipe_error", impl)]
2812    #[repr(transparent)]
2813    #[derive(Debug)]
2814    pub struct PyBrokenPipeError(PyConnectionError);
2815
2816    #[pyexception(
2817        name,
2818        base = PyConnectionError,
2819        ctx = "connection_aborted_error",
2820        impl
2821    )]
2822    #[repr(transparent)]
2823    #[derive(Debug)]
2824    pub struct PyConnectionAbortedError(PyConnectionError);
2825
2826    #[pyexception(
2827        name,
2828        base = PyConnectionError,
2829        ctx = "connection_refused_error",
2830        impl
2831    )]
2832    #[repr(transparent)]
2833    #[derive(Debug)]
2834    pub struct PyConnectionRefusedError(PyConnectionError);
2835
2836    #[pyexception(name, base = PyConnectionError, ctx = "connection_reset_error", impl)]
2837    #[repr(transparent)]
2838    #[derive(Debug)]
2839    pub struct PyConnectionResetError(PyConnectionError);
2840
2841    #[pyexception(name, base = PyOSError, ctx = "file_exists_error", impl)]
2842    #[repr(transparent)]
2843    #[derive(Debug)]
2844    pub struct PyFileExistsError(PyOSError);
2845
2846    #[pyexception(name, base = PyOSError, ctx = "file_not_found_error", impl)]
2847    #[repr(transparent)]
2848    #[derive(Debug)]
2849    pub struct PyFileNotFoundError(PyOSError);
2850
2851    #[pyexception(name, base = PyOSError, ctx = "interrupted_error", impl)]
2852    #[repr(transparent)]
2853    #[derive(Debug)]
2854    pub struct PyInterruptedError(PyOSError);
2855
2856    #[pyexception(name, base = PyOSError, ctx = "is_a_directory_error", impl)]
2857    #[repr(transparent)]
2858    #[derive(Debug)]
2859    pub struct PyIsADirectoryError(PyOSError);
2860
2861    #[pyexception(name, base = PyOSError, ctx = "not_a_directory_error", impl)]
2862    #[repr(transparent)]
2863    #[derive(Debug)]
2864    pub struct PyNotADirectoryError(PyOSError);
2865
2866    #[pyexception(name, base = PyOSError, ctx = "permission_error", impl)]
2867    #[repr(transparent)]
2868    #[derive(Debug)]
2869    pub struct PyPermissionError(PyOSError);
2870
2871    #[pyexception(name, base = PyOSError, ctx = "process_lookup_error", impl)]
2872    #[repr(transparent)]
2873    #[derive(Debug)]
2874    pub struct PyProcessLookupError(PyOSError);
2875
2876    #[pyexception(name, base = PyOSError, ctx = "timeout_error", impl)]
2877    #[derive(Debug)]
2878    #[repr(transparent)]
2879    pub struct PyTimeoutError(PyOSError);
2880
2881    #[pyexception(name, base = PyException, ctx = "reference_error", impl)]
2882    #[derive(Debug)]
2883    #[repr(transparent)]
2884    pub struct PyReferenceError(PyException);
2885
2886    #[pyexception(name, base = PyException, ctx = "runtime_error", impl)]
2887    #[derive(Debug)]
2888    #[repr(transparent)]
2889    pub struct PyRuntimeError(PyException);
2890
2891    #[pyexception(name, base = PyRuntimeError, ctx = "not_implemented_error", impl)]
2892    #[derive(Debug)]
2893    #[repr(transparent)]
2894    pub struct PyNotImplementedError(PyRuntimeError);
2895
2896    #[pyexception(name, base = PyRuntimeError, ctx = "recursion_error", impl)]
2897    #[derive(Debug)]
2898    #[repr(transparent)]
2899    pub struct PyRecursionError(PyRuntimeError);
2900
2901    #[pyexception(name, base = PyRuntimeError, ctx = "python_finalization_error", impl)]
2902    #[derive(Debug)]
2903    #[repr(transparent)]
2904    pub struct PyPythonFinalizationError(PyRuntimeError);
2905
2906    #[pyexception(name, base = PyException, ctx = "syntax_error", traverse = "manual")]
2907    #[repr(C)]
2908    pub struct PySyntaxError {
2909        base: PyException,
2910        #[pymember(writable)]
2911        msg: PyAtomicRef<Option<PyObject>>,
2912        #[pymember(writable)]
2913        filename: PyAtomicRef<Option<PyObject>>,
2914        #[pymember(writable)]
2915        lineno: PyAtomicRef<Option<PyObject>>,
2916        #[pymember(writable)]
2917        offset: PyAtomicRef<Option<PyObject>>,
2918        #[pymember(writable)]
2919        text: PyAtomicRef<Option<PyObject>>,
2920        #[pymember(writable)]
2921        end_lineno: PyAtomicRef<Option<PyObject>>,
2922        #[pymember(writable)]
2923        end_offset: PyAtomicRef<Option<PyObject>>,
2924        #[pymember(writable)]
2925        print_file_and_line: PyAtomicRef<Option<PyObject>>,
2926        #[pymember(name = "_metadata", writable)]
2927        metadata: PyAtomicRef<Option<PyObject>>,
2928    }
2929
2930    impl crate::class::PySubclass for PySyntaxError {
2931        type Base = PyException;
2932        fn as_base(&self) -> &Self::Base {
2933            &self.base
2934        }
2935    }
2936
2937    impl core::fmt::Debug for PySyntaxError {
2938        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2939            f.debug_struct("PySyntaxError").finish_non_exhaustive()
2940        }
2941    }
2942
2943    unsafe impl Traverse for PySyntaxError {
2944        fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
2945            self.base.0.traverse(tracer_fn);
2946            for cell in [
2947                &self.msg,
2948                &self.filename,
2949                &self.lineno,
2950                &self.offset,
2951                &self.text,
2952                &self.end_lineno,
2953                &self.end_offset,
2954                &self.print_file_and_line,
2955                &self.metadata,
2956            ] {
2957                if let Some(obj) = cell.deref() {
2958                    tracer_fn(obj);
2959                }
2960            }
2961        }
2962    }
2963
2964    impl Constructor for PySyntaxError {
2965        type Args = FuncArgs;
2966
2967        fn py_new(_cls: &Py<PyType>, args: FuncArgs, vm: &VirtualMachine) -> PyResult<Self> {
2968            Ok(Self {
2969                base: PyException(PyBaseException::new(args.args, vm)),
2970                msg: None.into(),
2971                filename: None.into(),
2972                lineno: None.into(),
2973                offset: None.into(),
2974                text: None.into(),
2975                end_lineno: None.into(),
2976                end_offset: None.into(),
2977                print_file_and_line: None.into(),
2978                metadata: None.into(),
2979            })
2980        }
2981    }
2982
2983    #[pyexception(with(Constructor, Initializer))]
2984    impl PySyntaxError {
2985        #[pyslot]
2986        fn slot_str(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
2987            let exc: &Py<Self> = zelf
2988                .downcast_ref()
2989                .expect("slot wrapper checked SyntaxError");
2990            fn basename(filename: &Wtf8) -> &Wtf8 {
2991                let bytes = filename.as_bytes();
2992                let pos = if cfg!(windows) {
2993                    bytes.iter().rposition(|&b| b == b'/' || b == b'\\')
2994                } else {
2995                    bytes.iter().rposition(|&b| b == b'/')
2996                };
2997                match pos {
2998                    // SAFETY: splitting at ASCII byte boundary preserves WTF-8 validity
2999                    Some(pos) => unsafe { Wtf8::from_bytes_unchecked(&bytes[pos + 1..]) },
3000                    None => filename,
3001                }
3002            }
3003
3004            let maybe_lineno = exc
3005                .lineno
3006                .load_owned()
3007                .filter(|obj| obj.class().is(vm.ctx.types.int_type))
3008                .and_then(|obj| {
3009                    obj.downcast_ref::<PyInt>()
3010                        .map(|int| int.as_bigint().to_string())
3011                });
3012            let maybe_filename = exc
3013                .filename
3014                .load_owned()
3015                .filter(|obj| obj.fast_isinstance(vm.ctx.types.str_type))
3016                .map(|obj| {
3017                    obj.str(vm)
3018                        .unwrap_or_else(|_| vm.ctx.new_str("<filename str() failed>"))
3019                });
3020
3021            let msg = match exc.msg.load_owned() {
3022                Some(obj) => obj
3023                    .str(vm)
3024                    .unwrap_or_else(|_| vm.ctx.new_str("<msg str() failed>")),
3025                None => vm.ctx.none().str(vm)?,
3026            };
3027
3028            let msg_with_location_info: Wtf8Buf = match (maybe_lineno, maybe_filename) {
3029                (Some(lineno), Some(filename)) => wtf8_concat!(
3030                    msg.as_wtf8(),
3031                    " (",
3032                    basename(filename.as_wtf8()),
3033                    ", line ",
3034                    lineno.as_str(),
3035                    ")"
3036                ),
3037                (Some(lineno), None) => {
3038                    wtf8_concat!(msg.as_wtf8(), " (line ", lineno.as_str(), ")")
3039                }
3040                (None, Some(filename)) => {
3041                    wtf8_concat!(msg.as_wtf8(), " (", basename(filename.as_wtf8()), ")")
3042                }
3043                (None, None) => msg.as_wtf8().to_owned(),
3044            };
3045
3046            Ok(vm.ctx.new_str(msg_with_location_info))
3047        }
3048    }
3049
3050    impl Initializer for PySyntaxError {
3051        type Args = FuncArgs;
3052
3053        fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
3054            let len = args.args.len();
3055            let new_args = args;
3056
3057            let mut filename = None;
3058            let mut lineno = None;
3059            let mut offset = None;
3060            let mut text = None;
3061            let mut end_lineno = None;
3062            let mut end_offset = None;
3063            let mut metadata = None;
3064
3065            let have_location = if len == 2
3066                && let Ok(location_tuple) = new_args.args[1]
3067                    .clone()
3068                    .downcast::<crate::builtins::PyTuple>()
3069            {
3070                let location_tup_len = location_tuple.as_slice().len();
3071
3072                match location_tup_len {
3073                    4 | 6 | 7 => {}
3074                    5 => {
3075                        return Err(vm.new_type_error(
3076                            "end_offset must be provided when end_lineno is provided",
3077                        ));
3078                    }
3079                    given if given < 4 => {
3080                        return Err(vm.new_type_error(format!(
3081                            "function takes at least 4 arguments ({given} given)"
3082                        )));
3083                    }
3084                    given => {
3085                        return Err(vm.new_type_error(format!(
3086                            "function takes at most 7 arguments ({given} given)"
3087                        )));
3088                    }
3089                }
3090
3091                let at = |index: usize| -> Option<PyObjectRef> {
3092                    (location_tup_len > index).then(|| location_tuple.as_slice()[index].to_owned())
3093                };
3094                filename = at(0);
3095                lineno = at(1);
3096                offset = at(2);
3097                text = at(3);
3098                end_lineno = at(4);
3099                end_offset = at(5);
3100                metadata = at(6);
3101                true
3102            } else {
3103                false
3104            };
3105
3106            PyBaseException::slot_init(zelf, new_args, vm)?;
3107            let exc: &Py<Self> = zelf
3108                .downcast_ref()
3109                .expect("SyntaxError instance has SyntaxError payload");
3110            if len >= 1 {
3111                exc.msg.swap_to_temporary_refs(
3112                    Some(exc.base.0.get_arg(0).expect("args len >= 1")),
3113                    vm,
3114                );
3115            }
3116            exc.print_file_and_line
3117                .swap_to_temporary_refs(Some(vm.ctx.none()), vm);
3118            if have_location {
3119                exc.filename.swap_to_temporary_refs(filename, vm);
3120                exc.lineno.swap_to_temporary_refs(lineno, vm);
3121                exc.offset.swap_to_temporary_refs(offset, vm);
3122                exc.text.swap_to_temporary_refs(text, vm);
3123                exc.end_lineno.swap_to_temporary_refs(end_lineno, vm);
3124                exc.end_offset.swap_to_temporary_refs(end_offset, vm);
3125                exc.metadata.swap_to_temporary_refs(metadata, vm);
3126            }
3127            Ok(())
3128        }
3129
3130        fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
3131            unreachable!("slot_init is defined")
3132        }
3133    }
3134
3135    // MiddlingExtendsException: inherits __init__ from SyntaxError via MRO
3136    #[pyexception(
3137        name = "_IncompleteInputError",
3138        base = PySyntaxError,
3139        ctx = "incomplete_input_error",
3140        impl
3141    )]
3142    #[derive(Debug)]
3143    #[repr(transparent)]
3144    pub struct PyIncompleteInputError(PySyntaxError);
3145
3146    #[pyexception(name, base = PySyntaxError, ctx = "indentation_error", impl)]
3147    #[derive(Debug)]
3148    #[repr(transparent)]
3149    pub struct PyIndentationError(PySyntaxError);
3150
3151    #[pyexception(name, base = PyIndentationError, ctx = "tab_error", impl)]
3152    #[derive(Debug)]
3153    #[repr(transparent)]
3154    pub struct PyTabError(PyIndentationError);
3155
3156    #[pyexception(name, base = PyException, ctx = "system_error", impl)]
3157    #[derive(Debug)]
3158    #[repr(transparent)]
3159    pub struct PySystemError(PyException);
3160
3161    #[pyexception(name, base = PyException, ctx = "type_error", impl)]
3162    #[derive(Debug)]
3163    #[repr(transparent)]
3164    pub struct PyTypeError(PyException);
3165
3166    #[pyexception(name, base = PyException, ctx = "value_error", impl)]
3167    #[derive(Debug)]
3168    #[repr(transparent)]
3169    pub struct PyValueError(PyException);
3170
3171    /// Check the fixed arity expected by the tuple parser before converting
3172    /// any of the values it was given.
3173    fn parse_tuple_arity(args: &FuncArgs, count: usize, vm: &VirtualMachine) -> PyResult<()> {
3174        let given = args.args.len();
3175        if given == count {
3176            return Ok(());
3177        }
3178        Err(vm.new_type_error(format!(
3179            "function takes exactly {count} arguments ({given} given)"
3180        )))
3181    }
3182
3183    fn unicode_error_payload(exc: &PyObject) -> &Py<PyUnicodeError> {
3184        exc.downcast_ref()
3185            .expect("slot wrapper checked the unicode error type")
3186    }
3187
3188    /// `PyObject_Str` of a nullable object member. A null pointer stringifies
3189    /// as `<NULL>`; a stored object uses its own `__str__`.
3190    fn unicode_cell_str(
3191        cell: &PyAtomicRef<Option<PyObject>>,
3192        vm: &VirtualMachine,
3193    ) -> PyResult<PyStrRef> {
3194        match cell.load_owned() {
3195            Some(obj) => obj.str(vm),
3196            None => Ok(vm.ctx.new_str("<NULL>")),
3197        }
3198    }
3199
3200    fn unicode_error_object(
3201        exc: &Py<PyUnicodeError>,
3202        as_bytes: bool,
3203        vm: &VirtualMachine,
3204    ) -> PyResult<PyObjectRef> {
3205        let Some(obj) = exc.object.load_owned() else {
3206            return Err(vm.new_type_error("UnicodeError 'object' attribute is not set"));
3207        };
3208        let ok = if as_bytes {
3209            obj.fast_isinstance(vm.ctx.types.bytes_type)
3210        } else {
3211            obj.fast_isinstance(vm.ctx.types.str_type)
3212        };
3213        if !ok {
3214            let kind = if as_bytes { "bytes" } else { "string" };
3215            return Err(
3216                vm.new_type_error(format!("UnicodeError 'object' attribute must be a {kind}"))
3217            );
3218        }
3219        Ok(obj)
3220    }
3221
3222    #[pyexception(name, base = PyValueError, ctx = "unicode_error", traverse = "manual")]
3223    #[repr(C)]
3224    pub struct PyUnicodeError {
3225        base: PyValueError,
3226        encoding: PyAtomicRef<Option<PyObject>>,
3227        object: PyAtomicRef<Option<PyObject>>,
3228        start: AtomicIsize,
3229        end: AtomicIsize,
3230        reason: PyAtomicRef<Option<PyObject>>,
3231    }
3232
3233    impl crate::class::PySubclass for PyUnicodeError {
3234        type Base = PyValueError;
3235        fn as_base(&self) -> &Self::Base {
3236            &self.base
3237        }
3238    }
3239
3240    impl core::fmt::Debug for PyUnicodeError {
3241        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3242            f.debug_struct("PyUnicodeError").finish_non_exhaustive()
3243        }
3244    }
3245
3246    unsafe impl Traverse for PyUnicodeError {
3247        fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
3248            self.base.0.0.traverse(tracer_fn);
3249            if let Some(obj) = self.encoding.deref() {
3250                tracer_fn(obj);
3251            }
3252            if let Some(obj) = self.object.deref() {
3253                tracer_fn(obj);
3254            }
3255            if let Some(obj) = self.reason.deref() {
3256                tracer_fn(obj);
3257            }
3258        }
3259    }
3260
3261    fn store_unicode_error(
3262        zelf: &PyObject,
3263        encoding: Option<PyObjectRef>,
3264        object: Option<PyObjectRef>,
3265        start: isize,
3266        end: isize,
3267        reason: Option<PyObjectRef>,
3268        vm: &VirtualMachine,
3269    ) {
3270        let exc: &Py<PyUnicodeError> = zelf
3271            .downcast_ref()
3272            .expect("unicode error subclass has PyUnicodeError payload");
3273        exc.encoding.swap_to_temporary_refs(encoding, vm);
3274        exc.object.swap_to_temporary_refs(object, vm);
3275        exc.start.store(start, Ordering::Relaxed);
3276        exc.end.store(end, Ordering::Relaxed);
3277        exc.reason.swap_to_temporary_refs(reason, vm);
3278    }
3279
3280    #[pyexception(with(Constructor))]
3281    impl PyUnicodeError {}
3282
3283    impl Constructor for PyUnicodeError {
3284        type Args = FuncArgs;
3285
3286        fn py_new(_cls: &Py<PyType>, args: FuncArgs, vm: &VirtualMachine) -> PyResult<Self> {
3287            Ok(Self {
3288                base: PyValueError(PyException(PyBaseException::new(args.args, vm))),
3289                encoding: None.into(),
3290                object: None.into(),
3291                start: AtomicIsize::new(0),
3292                end: AtomicIsize::new(0),
3293                reason: None.into(),
3294            })
3295        }
3296    }
3297
3298    #[pyexception(name, base = PyUnicodeError, ctx = "unicode_decode_error")]
3299    #[derive(Debug)]
3300    #[repr(transparent)]
3301    pub struct PyUnicodeDecodeError(
3302        #[pymember(name = "encoding", path = "encoding", writable)]
3303        #[pymember(name = "object", path = "object", writable)]
3304        #[pymember(name = "start", path = "start", writable)]
3305        #[pymember(name = "end", path = "end", writable)]
3306        #[pymember(name = "reason", path = "reason", writable)]
3307        PyUnicodeError,
3308    );
3309
3310    #[pyexception(with(Initializer))]
3311    impl PyUnicodeDecodeError {
3312        #[pyslot]
3313        fn slot_str(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
3314            let payload = unicode_error_payload(zelf);
3315            if payload.object.deref().is_none() {
3316                return Ok(vm.ctx.empty_str.to_owned());
3317            }
3318            let reason = unicode_cell_str(&payload.reason, vm)?;
3319            let encoding = unicode_cell_str(&payload.encoding, vm)?;
3320            let object: ArgBytesLike =
3321                unicode_error_object(payload, true, vm)?.try_into_value(vm)?;
3322            let start = payload.start.load(Ordering::Relaxed);
3323            let end = payload.end.load(Ordering::Relaxed);
3324            let start_u = usize::try_from(start).ok();
3325            let end_u = usize::try_from(end).ok();
3326            Ok(vm.ctx.new_str(
3327                if let (Some(start_u), Some(end_u)) = (start_u, end_u)
3328                    && start_u < object.len()
3329                    && end_u <= object.len()
3330                    && end_u == start_u + 1
3331                {
3332                    let b = object.borrow_buf()[start_u];
3333                    format!(
3334                        "'{encoding}' codec can't decode byte {b:#04x} in position {start}: {reason}"
3335                    )
3336                } else {
3337                    format!(
3338                        "'{encoding}' codec can't decode bytes in position {start}-{}: {reason}",
3339                        end - 1,
3340                    )
3341                },
3342            ))
3343        }
3344    }
3345
3346    impl Initializer for PyUnicodeDecodeError {
3347        type Args = FuncArgs;
3348
3349        fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
3350            parse_tuple_arity(&args, 5, vm)?;
3351            type Args = (PyStrRef, ArgBytesLike, isize, isize, PyStrRef);
3352            let (encoding, object, start, end, reason): Args = args.bind(vm)?;
3353            store_unicode_error(
3354                zelf,
3355                Some(encoding.into()),
3356                Some(vm.ctx.new_bytes(object.borrow_buf().to_vec()).into()),
3357                start,
3358                end,
3359                Some(reason.into()),
3360                vm,
3361            );
3362            Ok(())
3363        }
3364
3365        fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
3366            unreachable!("slot_init is defined")
3367        }
3368    }
3369
3370    #[pyexception(name, base = PyUnicodeError, ctx = "unicode_encode_error")]
3371    #[derive(Debug)]
3372    #[repr(transparent)]
3373    pub struct PyUnicodeEncodeError(
3374        #[pymember(name = "encoding", path = "encoding", writable)]
3375        #[pymember(name = "object", path = "object", writable)]
3376        #[pymember(name = "start", path = "start", writable)]
3377        #[pymember(name = "end", path = "end", writable)]
3378        #[pymember(name = "reason", path = "reason", writable)]
3379        PyUnicodeError,
3380    );
3381
3382    #[pyexception(with(Initializer))]
3383    impl PyUnicodeEncodeError {
3384        #[pyslot]
3385        fn slot_str(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
3386            let payload = unicode_error_payload(zelf);
3387            if payload.object.deref().is_none() {
3388                return Ok(vm.ctx.empty_str.to_owned());
3389            }
3390            let reason = unicode_cell_str(&payload.reason, vm)?;
3391            let encoding = unicode_cell_str(&payload.encoding, vm)?;
3392            let object: PyStrRef = unicode_error_object(payload, false, vm)?.try_into_value(vm)?;
3393            let start = payload.start.load(Ordering::Relaxed);
3394            let end = payload.end.load(Ordering::Relaxed);
3395            let start_u = usize::try_from(start).ok();
3396            let end_u = usize::try_from(end).ok();
3397            Ok(vm.ctx.new_str(
3398                if let (Some(start_u), Some(end_u)) = (start_u, end_u)
3399                    && start_u < object.char_len()
3400                    && end_u <= object.char_len()
3401                    && end_u == start_u + 1
3402                {
3403                    let ch = object.as_wtf8().code_points().nth(start_u).unwrap();
3404                    format!(
3405                        "'{encoding}' codec can't encode character '{}' in position {start}: {reason}",
3406                        UnicodeEscapeCodepoint(ch)
3407                    )
3408                } else {
3409                    format!(
3410                        "'{encoding}' codec can't encode characters in position {start}-{}: {reason}",
3411                        end - 1,
3412                    )
3413                },
3414            ))
3415        }
3416    }
3417
3418    impl Initializer for PyUnicodeEncodeError {
3419        type Args = FuncArgs;
3420
3421        fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
3422            parse_tuple_arity(&args, 5, vm)?;
3423            type Args = (PyStrRef, PyStrRef, isize, isize, PyStrRef);
3424            let (encoding, object, start, end, reason): Args = args.bind(vm)?;
3425            store_unicode_error(
3426                zelf,
3427                Some(encoding.into()),
3428                Some(object.into()),
3429                start,
3430                end,
3431                Some(reason.into()),
3432                vm,
3433            );
3434            Ok(())
3435        }
3436
3437        fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
3438            unreachable!("slot_init is defined")
3439        }
3440    }
3441
3442    #[pyexception(name, base = PyUnicodeError, ctx = "unicode_translate_error")]
3443    #[derive(Debug)]
3444    #[repr(transparent)]
3445    pub struct PyUnicodeTranslateError(
3446        #[pymember(name = "encoding", path = "encoding", writable)]
3447        #[pymember(name = "object", path = "object", writable)]
3448        #[pymember(name = "start", path = "start", writable)]
3449        #[pymember(name = "end", path = "end", writable)]
3450        #[pymember(name = "reason", path = "reason", writable)]
3451        PyUnicodeError,
3452    );
3453
3454    #[pyexception(with(Initializer))]
3455    impl PyUnicodeTranslateError {
3456        #[pyslot]
3457        fn slot_str(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
3458            let payload = unicode_error_payload(zelf);
3459            if payload.object.deref().is_none() {
3460                return Ok(vm.ctx.empty_str.to_owned());
3461            }
3462            let reason = unicode_cell_str(&payload.reason, vm)?;
3463            let object: PyStrRef = unicode_error_object(payload, false, vm)?.try_into_value(vm)?;
3464            let start = payload.start.load(Ordering::Relaxed);
3465            let end = payload.end.load(Ordering::Relaxed);
3466            let start_u = usize::try_from(start).ok();
3467            let end_u = usize::try_from(end).ok();
3468            Ok(vm.ctx.new_str(
3469                if let (Some(start_u), Some(end_u)) = (start_u, end_u)
3470                    && start_u < object.char_len()
3471                    && end_u <= object.char_len()
3472                    && end_u == start_u + 1
3473                {
3474                    let ch = object.as_wtf8().code_points().nth(start_u).unwrap();
3475                    format!(
3476                        "can't translate character '{}' in position {start}: {reason}",
3477                        UnicodeEscapeCodepoint(ch)
3478                    )
3479                } else {
3480                    format!(
3481                        "can't translate characters in position {start}-{}: {reason}",
3482                        end - 1,
3483                    )
3484                },
3485            ))
3486        }
3487    }
3488
3489    impl Initializer for PyUnicodeTranslateError {
3490        type Args = FuncArgs;
3491
3492        fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
3493            parse_tuple_arity(&args, 4, vm)?;
3494            type Args = (PyStrRef, isize, isize, PyStrRef);
3495            let (object, start, end, reason): Args = args.bind(vm)?;
3496            store_unicode_error(
3497                zelf,
3498                None,
3499                Some(object.into()),
3500                start,
3501                end,
3502                Some(reason.into()),
3503                vm,
3504            );
3505            Ok(())
3506        }
3507
3508        fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
3509            unreachable!("slot_init is defined")
3510        }
3511    }
3512
3513    // JIT error.
3514    #[cfg(feature = "jit")]
3515    #[pyexception(name, base = PyException, ctx = "jit_error", impl)]
3516    #[derive(Debug)]
3517    #[repr(transparent)]
3518    pub struct PyJitError(PyException);
3519
3520    // Warnings
3521    #[pyexception(name, base = PyException, ctx = "warning", impl)]
3522    #[derive(Debug)]
3523    #[repr(transparent)]
3524    pub struct PyWarning(PyException);
3525
3526    #[pyexception(name, base = PyWarning, ctx = "deprecation_warning", impl)]
3527    #[derive(Debug)]
3528    #[repr(transparent)]
3529    pub struct PyDeprecationWarning(PyWarning);
3530
3531    #[pyexception(name, base = PyWarning, ctx = "pending_deprecation_warning", impl)]
3532    #[derive(Debug)]
3533    #[repr(transparent)]
3534    pub struct PyPendingDeprecationWarning(PyWarning);
3535
3536    #[pyexception(name, base = PyWarning, ctx = "runtime_warning", impl)]
3537    #[derive(Debug)]
3538    #[repr(transparent)]
3539    pub struct PyRuntimeWarning(PyWarning);
3540
3541    #[pyexception(name, base = PyWarning, ctx = "syntax_warning", impl)]
3542    #[derive(Debug)]
3543    #[repr(transparent)]
3544    pub struct PySyntaxWarning(PyWarning);
3545
3546    #[pyexception(name, base = PyWarning, ctx = "user_warning", impl)]
3547    #[derive(Debug)]
3548    #[repr(transparent)]
3549    pub struct PyUserWarning(PyWarning);
3550
3551    #[pyexception(name, base = PyWarning, ctx = "future_warning", impl)]
3552    #[derive(Debug)]
3553    #[repr(transparent)]
3554    pub struct PyFutureWarning(PyWarning);
3555
3556    #[pyexception(name, base = PyWarning, ctx = "import_warning", impl)]
3557    #[derive(Debug)]
3558    #[repr(transparent)]
3559    pub struct PyImportWarning(PyWarning);
3560
3561    #[pyexception(name, base = PyWarning, ctx = "unicode_warning", impl)]
3562    #[derive(Debug)]
3563    #[repr(transparent)]
3564    pub struct PyUnicodeWarning(PyWarning);
3565
3566    #[pyexception(name, base = PyWarning, ctx = "bytes_warning", impl)]
3567    #[derive(Debug)]
3568    #[repr(transparent)]
3569    pub struct PyBytesWarning(PyWarning);
3570
3571    #[pyexception(name, base = PyWarning, ctx = "resource_warning", impl)]
3572    #[derive(Debug)]
3573    #[repr(transparent)]
3574    pub struct PyResourceWarning(PyWarning);
3575
3576    #[pyexception(name, base = PyWarning, ctx = "encoding_warning", impl)]
3577    #[derive(Debug)]
3578    #[repr(transparent)]
3579    pub struct PyEncodingWarning(PyWarning);
3580}
3581
3582/// Check if match_type is valid for except* (must be exception type, not ExceptionGroup).
3583fn check_except_star_type_valid(match_type: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
3584    let base_exc: PyObjectRef = vm.ctx.exceptions.base_exception_type.to_owned().into();
3585    let base_eg: PyObjectRef = vm.ctx.exceptions.base_exception_group.to_owned().into();
3586
3587    // Helper to check a single type
3588    let check_one = |exc_type: &PyObject| -> PyResult<()> {
3589        // Must be a subclass of BaseException
3590        if !exc_type.is_subclass(&base_exc, vm)? {
3591            return Err(vm.new_type_error(
3592                "catching classes that do not inherit from BaseException is not allowed",
3593            ));
3594        }
3595        // Must not be a subclass of BaseExceptionGroup
3596        if exc_type.is_subclass(&base_eg, vm)? {
3597            return Err(vm.new_type_error(
3598                "catching ExceptionGroup with except* is not allowed. Use except instead.",
3599            ));
3600        }
3601        Ok(())
3602    };
3603
3604    // If it's a tuple, check each element
3605    if let Ok(tuple) = match_type.to_owned().downcast::<PyTuple>() {
3606        for item in tuple.as_slice() {
3607            check_one(item)?;
3608        }
3609    } else {
3610        check_one(match_type)?;
3611    }
3612    Ok(())
3613}
3614
3615/// Match exception against except* handler type.
3616/// Returns (rest, match) tuple.
3617pub fn exception_group_match(
3618    exc_value: &PyObject,
3619    match_type: &PyObject,
3620    vm: &VirtualMachine,
3621) -> PyResult<(PyObjectRef, PyObjectRef)> {
3622    // Implements _PyEval_ExceptionGroupMatch
3623
3624    // If exc_value is None, return (None, None)
3625    if vm.is_none(exc_value) {
3626        return Ok((vm.ctx.none(), vm.ctx.none()));
3627    }
3628
3629    // Validate match_type and reject ExceptionGroup/BaseExceptionGroup
3630    check_except_star_type_valid(match_type, vm)?;
3631
3632    // Check if exc_value matches match_type
3633    if exc_value.is_instance(match_type, vm)? {
3634        // Full match of exc itself
3635        let is_eg = exc_value.fast_isinstance(vm.ctx.exceptions.base_exception_group);
3636        let matched = if is_eg {
3637            exc_value.to_owned()
3638        } else {
3639            // Naked exception - wrap it in ExceptionGroup
3640            let excs = vm.ctx.new_tuple(vec![exc_value.to_owned()]);
3641            let eg_type: PyObjectRef = crate::exception_group::exception_group().to_owned().into();
3642            let wrapped = eg_type.call((vm.ctx.new_str(""), excs), vm)?;
3643            // Copy traceback from original exception
3644            if let Ok(exc) = exc_value.to_owned().downcast::<types::PyBaseException>()
3645                && let Some(tb) = exc.traceback()
3646                && let Ok(wrapped_exc) = wrapped.clone().downcast::<types::PyBaseException>()
3647            {
3648                wrapped_exc.set_traceback(Some(tb));
3649            }
3650            wrapped
3651        };
3652        return Ok((vm.ctx.none(), matched));
3653    }
3654
3655    // Check for partial match if it's an exception group
3656    if exc_value.fast_isinstance(vm.ctx.exceptions.base_exception_group) {
3657        let pair = vm.call_method(exc_value, "split", (match_type.to_owned(),))?;
3658        if !pair.class().is(vm.ctx.types.tuple_type) {
3659            return Err(vm.new_type_error(format!(
3660                "{}.split must return a tuple, not {}",
3661                exc_value.class().name(),
3662                pair.class().name()
3663            )));
3664        }
3665        let pair_tuple: PyTupleRef = pair.try_into_value(vm)?;
3666        if pair_tuple.as_slice().len() < 2 {
3667            return Err(vm.new_type_error(format!(
3668                "{}.split must return a 2-tuple, got tuple of size {}",
3669                exc_value.class().name(),
3670                pair_tuple.as_slice().len()
3671            )));
3672        }
3673        let matched = pair_tuple.as_slice()[0].clone();
3674        let rest = pair_tuple.as_slice()[1].clone();
3675        return Ok((rest, matched));
3676    }
3677
3678    // No match
3679    Ok((exc_value.to_owned(), vm.ctx.none()))
3680}
3681
3682/// Prepare exception for reraise in except* block.
3683/// Implements _PyExc_PrepReraiseStar
3684pub fn prep_reraise_star(orig: &PyObject, excs: &PyObject, vm: &VirtualMachine) -> PyResult {
3685    use crate::builtins::PyList;
3686
3687    let excs_list = excs
3688        .downcast_ref::<PyList>()
3689        .ok_or_else(|| vm.new_type_error("expected list for prep_reraise_star"))?;
3690
3691    let excs_vec: Vec<PyObjectRef> = excs_list.borrow_vec().to_vec();
3692
3693    // If no exceptions to process, return None
3694    if excs_vec.is_empty() {
3695        return Ok(vm.ctx.none());
3696    }
3697
3698    // Special case: naked exception (not an ExceptionGroup)
3699    // Only one except* clause could have executed, so there's at most one exception to raise
3700    if !orig.fast_isinstance(vm.ctx.exceptions.base_exception_group) {
3701        // Find first non-None exception
3702        let first = excs_vec.into_iter().find(|e| !vm.is_none(e));
3703        return Ok(first.unwrap_or_else(|| vm.ctx.none()));
3704    }
3705
3706    // Split excs into raised (new) and reraised (from original) by comparing metadata
3707    let mut raised: Vec<PyObjectRef> = Vec::new();
3708    let mut reraised: Vec<PyObjectRef> = Vec::new();
3709
3710    for exc in excs_vec {
3711        if vm.is_none(&exc) {
3712            continue;
3713        }
3714        // Check if this exception came from the original group
3715        if is_exception_from_orig(&exc, orig, vm) {
3716            reraised.push(exc);
3717        } else {
3718            raised.push(exc);
3719        }
3720    }
3721
3722    // If no exceptions to reraise, return None
3723    if raised.is_empty() && reraised.is_empty() {
3724        return Ok(vm.ctx.none());
3725    }
3726
3727    // Project reraised exceptions onto original structure to preserve nesting
3728    let reraised_eg = exception_group_projection(orig, &reraised, vm)?;
3729
3730    // If no new raised exceptions, just return the reraised projection
3731    if raised.is_empty() {
3732        return Ok(reraised_eg);
3733    }
3734
3735    // Combine raised with reraised_eg
3736    if !vm.is_none(&reraised_eg) {
3737        raised.push(reraised_eg);
3738    }
3739
3740    // If only one exception, return it directly
3741    if raised.len() == 1 {
3742        return Ok(raised.into_iter().next().unwrap());
3743    }
3744
3745    // Create new ExceptionGroup for multiple exceptions
3746    let excs_tuple = vm.ctx.new_tuple(raised);
3747    let eg_type: PyObjectRef = crate::exception_group::exception_group().to_owned().into();
3748    eg_type.call((vm.ctx.new_str(""), excs_tuple), vm)
3749}
3750
3751/// Check if an exception came from the original group (for reraise detection).
3752/// Instead of comparing metadata (which can be modified when caught), we compare
3753/// leaf exception object IDs. split() preserves leaf exception identity.
3754fn is_exception_from_orig(exc: &PyObject, orig: &PyObject, vm: &VirtualMachine) -> bool {
3755    // Collect leaf exception IDs from exc
3756    let mut exc_leaf_ids = HashSet::new();
3757    collect_exception_group_leaf_ids(exc, &mut exc_leaf_ids, vm);
3758
3759    if exc_leaf_ids.is_empty() {
3760        return false;
3761    }
3762
3763    // Collect leaf exception IDs from orig
3764    let mut orig_leaf_ids = HashSet::new();
3765    collect_exception_group_leaf_ids(orig, &mut orig_leaf_ids, vm);
3766
3767    // If ALL of exc's leaves are in orig's leaves, it's a reraise
3768    exc_leaf_ids.iter().all(|id| orig_leaf_ids.contains(id))
3769}
3770
3771/// Collect all leaf exception IDs from an exception (group).
3772fn collect_exception_group_leaf_ids(
3773    exc: &PyObject,
3774    leaf_ids: &mut HashSet<usize>,
3775    vm: &VirtualMachine,
3776) {
3777    if vm.is_none(exc) {
3778        return;
3779    }
3780
3781    // If not an exception group, it's a leaf - add its ID
3782    if !exc.fast_isinstance(vm.ctx.exceptions.base_exception_group) {
3783        leaf_ids.insert(exc.get_id());
3784        return;
3785    }
3786
3787    // Recurse into exception group's exceptions
3788    if let Ok(excs_attr) = exc.get_attr("exceptions", vm)
3789        && let Ok(tuple) = excs_attr.downcast::<PyTuple>()
3790    {
3791        for e in tuple.as_slice() {
3792            collect_exception_group_leaf_ids(e, leaf_ids, vm);
3793        }
3794    }
3795}
3796
3797/// Project orig onto keep list, preserving nested structure.
3798/// Returns an exception group containing only the exceptions from orig
3799/// that are also in the keep list.
3800fn exception_group_projection(
3801    orig: &PyObject,
3802    keep: &[PyObjectRef],
3803    vm: &VirtualMachine,
3804) -> PyResult {
3805    if keep.is_empty() {
3806        return Ok(vm.ctx.none());
3807    }
3808
3809    // Collect all leaf IDs from keep list
3810    let mut leaf_ids = HashSet::new();
3811    for e in keep {
3812        collect_exception_group_leaf_ids(e, &mut leaf_ids, vm);
3813    }
3814
3815    // Split orig by matching leaf IDs, preserving structure
3816    split_by_leaf_ids(orig, &leaf_ids, vm)
3817}
3818
3819/// Recursively split an exception (group) by leaf IDs.
3820/// Returns the projection containing only matching leaves with preserved structure.
3821fn split_by_leaf_ids(exc: &PyObject, leaf_ids: &HashSet<usize>, vm: &VirtualMachine) -> PyResult {
3822    if vm.is_none(exc) {
3823        return Ok(vm.ctx.none());
3824    }
3825
3826    // If not an exception group, check if it's in our set
3827    if !exc.fast_isinstance(vm.ctx.exceptions.base_exception_group) {
3828        if leaf_ids.contains(&exc.get_id()) {
3829            return Ok(exc.to_owned());
3830        }
3831        return Ok(vm.ctx.none());
3832    }
3833
3834    // Exception group - recurse and reconstruct
3835    let excs_attr = exc.get_attr("exceptions", vm)?;
3836    let tuple: PyTupleRef = excs_attr.try_into_value(vm)?;
3837
3838    let mut matched = Vec::new();
3839    for e in tuple.as_slice() {
3840        let m = split_by_leaf_ids(e, leaf_ids, vm)?;
3841        if !vm.is_none(&m) {
3842            matched.push(m);
3843        }
3844    }
3845
3846    if matched.is_empty() {
3847        return Ok(vm.ctx.none());
3848    }
3849
3850    let group = exc
3851        .downcast_ref::<crate::exception_group::types::PyBaseExceptionGroup>()
3852        .ok_or_else(|| vm.new_type_error("expected a BaseExceptionGroup"))?;
3853    crate::exception_group::types::derive_and_copy_attributes(group, matched, vm)
3854}