mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
gh-105071: add PyUnstable_Exc_PrepReraiseStar to expose except* implementation in the unstable API (#105072)
This commit is contained in:
parent
bd98b65e97
commit
b7aadb4583
7 changed files with 196 additions and 1 deletions
|
@ -772,6 +772,16 @@ Exception Objects
|
||||||
|
|
||||||
Set :attr:`~BaseException.args` of exception *ex* to *args*.
|
Set :attr:`~BaseException.args` of exception *ex* to *args*.
|
||||||
|
|
||||||
|
.. c:function:: PyObject* PyUnstable_Exc_PrepReraiseStar(PyObject *orig, PyObject *excs)
|
||||||
|
|
||||||
|
Implement part of the interpreter's implementation of :keyword:`!except*`.
|
||||||
|
*orig* is the original exception that was caught, and *excs* is the list of
|
||||||
|
the exceptions that need to be raised. This list contains the the unhandled
|
||||||
|
part of *orig*, if any, as well as the exceptions that were raised from the
|
||||||
|
:keyword:`!except*` clauses (so they have a different traceback from *orig*) and
|
||||||
|
those that were reraised (and have the same traceback as *orig*).
|
||||||
|
Return the :exc:`ExceptionGroup` that needs to be reraised in the end, or
|
||||||
|
``None`` if there is nothing to reraise.
|
||||||
|
|
||||||
.. _unicodeexceptions:
|
.. _unicodeexceptions:
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,10 @@ PyAPI_FUNC(int) _PyException_AddNote(
|
||||||
PyObject *exc,
|
PyObject *exc,
|
||||||
PyObject *note);
|
PyObject *note);
|
||||||
|
|
||||||
|
PyAPI_FUNC(PyObject*) PyUnstable_Exc_PrepReraiseStar(
|
||||||
|
PyObject *orig,
|
||||||
|
PyObject *excs);
|
||||||
|
|
||||||
/* In signalmodule.c */
|
/* In signalmodule.c */
|
||||||
|
|
||||||
int PySignal_SetWakeupFd(int fd);
|
int PySignal_SetWakeupFd(int fd);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import unittest
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import import_helper
|
from test.support import import_helper
|
||||||
from test.support.script_helper import assert_python_failure
|
from test.support.script_helper import assert_python_failure
|
||||||
|
from test.support.testcase import ExceptionIsLikeMixin
|
||||||
|
|
||||||
from .test_misc import decode_stderr
|
from .test_misc import decode_stderr
|
||||||
|
|
||||||
|
@ -189,5 +190,97 @@ class Test_ErrSetAndRestore(unittest.TestCase):
|
||||||
'Normalization failed: type=Broken args=<unknown>')
|
'Normalization failed: type=Broken args=<unknown>')
|
||||||
|
|
||||||
|
|
||||||
|
class Test_PyUnstable_Exc_PrepReraiseStar(ExceptionIsLikeMixin, unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
try:
|
||||||
|
raise ExceptionGroup("eg", [TypeError('bad type'), ValueError(42)])
|
||||||
|
except ExceptionGroup as e:
|
||||||
|
self.orig = e
|
||||||
|
|
||||||
|
def test_invalid_args(self):
|
||||||
|
with self.assertRaisesRegex(TypeError, "orig must be an exception"):
|
||||||
|
_testcapi.unstable_exc_prep_reraise_star(42, [None])
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(TypeError, "excs must be a list"):
|
||||||
|
_testcapi.unstable_exc_prep_reraise_star(self.orig, 42)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(TypeError, "not an exception"):
|
||||||
|
_testcapi.unstable_exc_prep_reraise_star(self.orig, [TypeError(42), 42])
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "orig must be a raised exception"):
|
||||||
|
_testcapi.unstable_exc_prep_reraise_star(ValueError(42), [TypeError(42)])
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "orig must be a raised exception"):
|
||||||
|
_testcapi.unstable_exc_prep_reraise_star(ExceptionGroup("eg", [ValueError(42)]),
|
||||||
|
[TypeError(42)])
|
||||||
|
|
||||||
|
|
||||||
|
def test_nothing_to_reraise(self):
|
||||||
|
self.assertEqual(
|
||||||
|
_testcapi.unstable_exc_prep_reraise_star(self.orig, [None]), None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
raise ValueError(42)
|
||||||
|
except ValueError as e:
|
||||||
|
orig = e
|
||||||
|
self.assertEqual(
|
||||||
|
_testcapi.unstable_exc_prep_reraise_star(orig, [None]), None)
|
||||||
|
|
||||||
|
def test_reraise_orig(self):
|
||||||
|
orig = self.orig
|
||||||
|
res = _testcapi.unstable_exc_prep_reraise_star(orig, [orig])
|
||||||
|
self.assertExceptionIsLike(res, orig)
|
||||||
|
|
||||||
|
def test_raise_orig_parts(self):
|
||||||
|
orig = self.orig
|
||||||
|
match, rest = orig.split(TypeError)
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
([match, rest], orig),
|
||||||
|
([rest, match], orig),
|
||||||
|
([match], match),
|
||||||
|
([rest], rest),
|
||||||
|
([], None),
|
||||||
|
]
|
||||||
|
|
||||||
|
for input, expected in test_cases:
|
||||||
|
with self.subTest(input=input):
|
||||||
|
res = _testcapi.unstable_exc_prep_reraise_star(orig, input)
|
||||||
|
self.assertExceptionIsLike(res, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_raise_with_new_exceptions(self):
|
||||||
|
orig = self.orig
|
||||||
|
|
||||||
|
match, rest = orig.split(TypeError)
|
||||||
|
new1 = OSError('bad file')
|
||||||
|
new2 = RuntimeError('bad runtime')
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
([new1, match, rest], ExceptionGroup("", [new1, orig])),
|
||||||
|
([match, new1, rest], ExceptionGroup("", [new1, orig])),
|
||||||
|
([match, rest, new1], ExceptionGroup("", [new1, orig])),
|
||||||
|
|
||||||
|
([new1, new2, match, rest], ExceptionGroup("", [new1, new2, orig])),
|
||||||
|
([new1, match, new2, rest], ExceptionGroup("", [new1, new2, orig])),
|
||||||
|
([new2, rest, match, new1], ExceptionGroup("", [new2, new1, orig])),
|
||||||
|
([rest, new2, match, new1], ExceptionGroup("", [new2, new1, orig])),
|
||||||
|
|
||||||
|
|
||||||
|
([new1, new2, rest], ExceptionGroup("", [new1, new2, rest])),
|
||||||
|
([new1, match, new2], ExceptionGroup("", [new1, new2, match])),
|
||||||
|
([rest, new2, new1], ExceptionGroup("", [new2, new1, rest])),
|
||||||
|
([new1, new2], ExceptionGroup("", [new1, new2])),
|
||||||
|
([new2, new1], ExceptionGroup("", [new2, new1])),
|
||||||
|
]
|
||||||
|
|
||||||
|
for (input, expected) in test_cases:
|
||||||
|
with self.subTest(input=input):
|
||||||
|
res = _testcapi.unstable_exc_prep_reraise_star(orig, input)
|
||||||
|
self.assertExceptionIsLike(res, expected)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Add ``PyUnstable_Exc_PrepReraiseStar`` to the unstable C api to expose the implementation of :keyword:`except* <except_star>`.
|
33
Modules/_testcapi/clinic/exceptions.c.h
generated
33
Modules/_testcapi/clinic/exceptions.c.h
generated
|
@ -395,4 +395,35 @@ _testcapi_traceback_print(PyObject *module, PyObject *const *args, Py_ssize_t na
|
||||||
exit:
|
exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
/*[clinic end generated code: output=ec1b2e62adea9846 input=a9049054013a1b77]*/
|
|
||||||
|
PyDoc_STRVAR(_testcapi_unstable_exc_prep_reraise_star__doc__,
|
||||||
|
"unstable_exc_prep_reraise_star($module, orig, excs, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"To test PyUnstable_Exc_PrepReraiseStar.");
|
||||||
|
|
||||||
|
#define _TESTCAPI_UNSTABLE_EXC_PREP_RERAISE_STAR_METHODDEF \
|
||||||
|
{"unstable_exc_prep_reraise_star", _PyCFunction_CAST(_testcapi_unstable_exc_prep_reraise_star), METH_FASTCALL, _testcapi_unstable_exc_prep_reraise_star__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_testcapi_unstable_exc_prep_reraise_star_impl(PyObject *module,
|
||||||
|
PyObject *orig, PyObject *excs);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_testcapi_unstable_exc_prep_reraise_star(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
PyObject *orig;
|
||||||
|
PyObject *excs;
|
||||||
|
|
||||||
|
if (!_PyArg_CheckPositional("unstable_exc_prep_reraise_star", nargs, 2, 2)) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
orig = args[0];
|
||||||
|
excs = args[1];
|
||||||
|
return_value = _testcapi_unstable_exc_prep_reraise_star_impl(module, orig, excs);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
/*[clinic end generated code: output=fd6aef54f195c77b input=a9049054013a1b77]*/
|
||||||
|
|
|
@ -288,6 +288,22 @@ _testcapi_traceback_print_impl(PyObject *module, PyObject *traceback,
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_testcapi.unstable_exc_prep_reraise_star
|
||||||
|
orig: object
|
||||||
|
excs: object
|
||||||
|
/
|
||||||
|
To test PyUnstable_Exc_PrepReraiseStar.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_testcapi_unstable_exc_prep_reraise_star_impl(PyObject *module,
|
||||||
|
PyObject *orig, PyObject *excs)
|
||||||
|
/*[clinic end generated code: output=850cf008e0563c77 input=27fbcda2203eb301]*/
|
||||||
|
{
|
||||||
|
return PyUnstable_Exc_PrepReraiseStar(orig, excs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Define the PyRecurdingInfinitelyError_Type
|
* Define the PyRecurdingInfinitelyError_Type
|
||||||
|
@ -328,6 +344,7 @@ static PyMethodDef test_methods[] = {
|
||||||
_TESTCAPI_SET_EXCEPTION_METHODDEF
|
_TESTCAPI_SET_EXCEPTION_METHODDEF
|
||||||
_TESTCAPI_TRACEBACK_PRINT_METHODDEF
|
_TESTCAPI_TRACEBACK_PRINT_METHODDEF
|
||||||
_TESTCAPI_WRITE_UNRAISABLE_EXC_METHODDEF
|
_TESTCAPI_WRITE_UNRAISABLE_EXC_METHODDEF
|
||||||
|
_TESTCAPI_UNSTABLE_EXC_PREP_RERAISE_STAR_METHODDEF
|
||||||
{NULL},
|
{NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1351,7 +1351,10 @@ is_same_exception_metadata(PyObject *exc1, PyObject *exc2)
|
||||||
PyObject *
|
PyObject *
|
||||||
_PyExc_PrepReraiseStar(PyObject *orig, PyObject *excs)
|
_PyExc_PrepReraiseStar(PyObject *orig, PyObject *excs)
|
||||||
{
|
{
|
||||||
|
/* orig must be a raised & caught exception, so it has a traceback */
|
||||||
assert(PyExceptionInstance_Check(orig));
|
assert(PyExceptionInstance_Check(orig));
|
||||||
|
assert(_PyBaseExceptionObject_cast(orig)->traceback != NULL);
|
||||||
|
|
||||||
assert(PyList_Check(excs));
|
assert(PyList_Check(excs));
|
||||||
|
|
||||||
Py_ssize_t numexcs = PyList_GET_SIZE(excs);
|
Py_ssize_t numexcs = PyList_GET_SIZE(excs);
|
||||||
|
@ -1438,6 +1441,42 @@ done:
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
PyUnstable_Exc_PrepReraiseStar(PyObject *orig, PyObject *excs)
|
||||||
|
{
|
||||||
|
if (orig == NULL || !PyExceptionInstance_Check(orig)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "orig must be an exception instance");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (excs == NULL || !PyList_Check(excs)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"excs must be a list of exception instances");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_ssize_t numexcs = PyList_GET_SIZE(excs);
|
||||||
|
for (Py_ssize_t i = 0; i < numexcs; i++) {
|
||||||
|
PyObject *exc = PyList_GET_ITEM(excs, i);
|
||||||
|
if (exc == NULL || !(PyExceptionInstance_Check(exc) || Py_IsNone(exc))) {
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"item %d of excs is not an exception", i);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure that orig has something as traceback, in the interpreter
|
||||||
|
* it always does becuase it's a raised exception.
|
||||||
|
*/
|
||||||
|
PyObject *tb = PyException_GetTraceback(orig);
|
||||||
|
|
||||||
|
if (tb == NULL) {
|
||||||
|
PyErr_Format(PyExc_ValueError, "orig must be a raised exception");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_DECREF(tb);
|
||||||
|
|
||||||
|
return _PyExc_PrepReraiseStar(orig, excs);
|
||||||
|
}
|
||||||
|
|
||||||
static PyMemberDef BaseExceptionGroup_members[] = {
|
static PyMemberDef BaseExceptionGroup_members[] = {
|
||||||
{"message", T_OBJECT, offsetof(PyBaseExceptionGroupObject, msg), READONLY,
|
{"message", T_OBJECT, offsetof(PyBaseExceptionGroupObject, msg), READONLY,
|
||||||
PyDoc_STR("exception message")},
|
PyDoc_STR("exception message")},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue