Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -3060,7 +3060,6 @@ def exc():
report = self.get_report(exc)
self.assertEqual(report, expected)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_exception_group_wrapped_naked(self):
# See gh-128799

Expand Down
20 changes: 7 additions & 13 deletions crates/vm/src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3758,6 +3758,8 @@ fn check_except_star_type_valid(match_type: &PyObject, vm: &VirtualMachine) -> P

/// Match exception against except* handler type.
/// Returns (rest, match) tuple.
/// A caller matching a naked exception must attach the handler-frame
/// traceback to the resulting wrapper before exposing it.
pub fn exception_group_match(
exc_value: &PyObject,
match_type: &PyObject,
Expand All @@ -3780,18 +3782,10 @@ pub fn exception_group_match(
let matched = if is_eg {
exc_value.to_owned()
} else {
// Naked exception - wrap it in ExceptionGroup
// Naked exception: let BaseExceptionGroup choose the group type.
let excs = vm.ctx.new_tuple(vec![exc_value.to_owned()]);
let eg_type: PyObjectRef = crate::exception_group::exception_group().to_owned().into();
let wrapped = eg_type.call((vm.ctx.new_str(""), excs), vm)?;
// Copy traceback from original exception
if let Ok(exc) = exc_value.to_owned().downcast::<types::PyBaseException>()
&& let Some(tb) = exc.traceback()
&& let Ok(wrapped_exc) = wrapped.clone().downcast::<types::PyBaseException>()
{
wrapped_exc.set_traceback(Some(tb));
}
wrapped
let eg_type: PyObjectRef = vm.ctx.exceptions.base_exception_group.to_owned().into();
eg_type.call((vm.ctx.new_str(""), excs), vm)?
};
return Ok((vm.ctx.none(), matched));
}
Expand Down Expand Up @@ -3886,9 +3880,9 @@ pub fn prep_reraise_star(orig: &PyObject, excs: &PyObject, vm: &VirtualMachine)
return Ok(raised.into_iter().next().unwrap());
}

// Create new ExceptionGroup for multiple exceptions
// Let BaseExceptionGroup select ExceptionGroup when all children permit it.
let excs_tuple = vm.ctx.new_tuple(raised);
let eg_type: PyObjectRef = crate::exception_group::exception_group().to_owned().into();
let eg_type: PyObjectRef = vm.ctx.exceptions.base_exception_group.to_owned().into();
eg_type.call((vm.ctx.new_str(""), excs_tuple), vm)
}

Expand Down
17 changes: 17 additions & 0 deletions crates/vm/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4658,6 +4658,8 @@ impl ExecutingFrame<'_> {
Instruction::CheckEgMatch => {
let match_type = self.pop_value();
let exc_value = self.pop_value();
let naked = !vm.is_none(&exc_value)
&& !exc_value.fast_isinstance(vm.ctx.exceptions.base_exception_group);
let (rest, matched) =
crate::exceptions::exception_group_match(&exc_value, &match_type, vm)?;

Expand All @@ -4666,6 +4668,21 @@ impl ExecutingFrame<'_> {
if !vm.is_none(&matched)
&& let Some(exc) = matched.downcast_ref::<PyBaseException>()
{
if naked {
// CPython gives a newly wrapped exception its own
// traceback at the except* handler, not the child's
// original traceback.
let idx = self.lasti().saturating_sub(1) as usize;
if let Some((loc, _)) = self.code.locations.get(idx) {
let tb = PyTraceback::new(
None,
self.frame_object(vm),
idx as i32 * 2,
loc.line,
);
exc.set_traceback(Some(tb.into_ref(&vm.ctx)));
}
}
vm.set_exception(Some(exc.to_owned()));
}

Expand Down
Loading