-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Return types for (Base)ExceptionGroup.derive()
, .subgroup()
, and .split()
should not use Self
#9219
Comments
Hi @Zac-HD! Glad to see you in typing-land! 👋 Related PRs: Yes, looks like >>> class MyGroup(ExceptionGroup):
... pass
>>> mg = MyGroup("msg", [ValueError(), TypeError()]) split>>> mg.split(ValueError)
(ExceptionGroup('msg', [ValueError()]), ExceptionGroup('msg', [TypeError()]))
>>> mg.split((UnicodeDecodeError, UnicodeEncodeError, TypeError))
(ExceptionGroup('msg', [TypeError()]), ExceptionGroup('msg', [ValueError()]))
>>> mg.split(IndexError)
(None, ExceptionGroup('msg', [ValueError(), TypeError()]))
>>> mg.split(lambda e: True)
(MyGroup('msg', [ValueError(), TypeError()]), None)
>>> mg.split(lambda e: isinstance(e, ValueError))
(ExceptionGroup('msg', [ValueError()]), ExceptionGroup('msg', [TypeError()]))
derive>>> b = BaseExceptionGroup('m', [ValueError()])
>>> b.derive([TypeError()]) # upcast
ExceptionGroup('m', [TypeError()])
>>> mg = MyGroup("msg", [ValueError(), TypeError()])
>>> mg.derive([IndexError()])
ExceptionGroup('msg', [IndexError()])
>>> mg.derive([ValueError(), TypeError()]) # exactly the same args
ExceptionGroup('msg', [ValueError(), TypeError()]) I cannot find a case, where subgroup>>> mg.subgroup(lambda e: True)
MyGroup('msg', [ValueError(), TypeError()])
>>> mg.subgroup(lambda e: False)
>>>
>>> mg.subgroup(TypeError)
ExceptionGroup('msg', [TypeError()])
>>> b = BaseExceptionGroup('m', [ValueError()])
>>> b.subgroup(TypeError)
>>> b.subgroup(ValueError)
ExceptionGroup('m', [ValueError()]) So, it can either be I would like to work on it, if no one has objections. |
I only mostly stick to runtime evil tricks, good to be here 😁 That all sounds good; just note under the derive examples that when |
I will take a look, I've missed this part. Thanks! |
For the record: static PyObject *
BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
struct _Py_exc_state *state = get_exc_state();
PyTypeObject *PyExc_ExceptionGroup =
(PyTypeObject*)state->PyExc_ExceptionGroup;
PyObject *message = NULL;
PyObject *exceptions = NULL;
if (!PyArg_ParseTuple(args,
"UO:BaseExceptionGroup.__new__",
&message,
&exceptions)) {
return NULL;
}
if (!PySequence_Check(exceptions)) {
PyErr_SetString(
PyExc_TypeError,
"second argument (exceptions) must be a sequence");
return NULL;
}
exceptions = PySequence_Tuple(exceptions);
if (!exceptions) {
return NULL;
}
/* We are now holding a ref to the exceptions tuple */
Py_ssize_t numexcs = PyTuple_GET_SIZE(exceptions);
if (numexcs == 0) {
PyErr_SetString(
PyExc_ValueError,
"second argument (exceptions) must be a non-empty sequence");
goto error;
}
bool nested_base_exceptions = false;
for (Py_ssize_t i = 0; i < numexcs; i++) {
PyObject *exc = PyTuple_GET_ITEM(exceptions, i);
if (!exc) {
goto error;
}
if (!PyExceptionInstance_Check(exc)) {
PyErr_Format(
PyExc_ValueError,
"Item %d of second argument (exceptions) is not an exception",
i);
goto error;
}
int is_nonbase_exception = PyObject_IsInstance(exc, PyExc_Exception);
if (is_nonbase_exception < 0) {
goto error;
}
else if (is_nonbase_exception == 0) {
nested_base_exceptions = true;
}
}
PyTypeObject *cls = type;
if (cls == PyExc_ExceptionGroup) {
if (nested_base_exceptions) {
PyErr_SetString(PyExc_TypeError,
"Cannot nest BaseExceptions in an ExceptionGroup");
goto error;
}
}
else if (cls == (PyTypeObject*)PyExc_BaseExceptionGroup) {
if (!nested_base_exceptions) {
/* All nested exceptions are Exception subclasses,
* wrap them in an ExceptionGroup
*/
cls = PyExc_ExceptionGroup;
}
}
else {
/* Do nothing - we don't interfere with subclasses */
}
if (!cls) {
/* Don't crash during interpreter shutdown
* (PyExc_ExceptionGroup may have been cleared)
*/
cls = (PyTypeObject*)PyExc_BaseExceptionGroup;
}
PyBaseExceptionGroupObject *self =
_PyBaseExceptionGroupObject_cast(BaseException_new(cls, args, kwds));
if (!self) {
goto error;
}
self->msg = Py_NewRef(message);
self->excs = exceptions;
return (PyObject*)self;
error:
Py_DECREF(exceptions);
return NULL;
} |
Looks like I found a bug in >>> b = BaseExceptionGroup('m', [TypeError(), OSError(1, 2)])
>>> def p(e):
... print(type(e), e)
... return True
...
>>> b.subgroup(p)
<class 'ExceptionGroup'> m (2 sub-exceptions)
ExceptionGroup('m', [TypeError(), PermissionError(1, 2)]) Notice that
While the docs say:
|
The same happens with >>> b.split(p)
<class 'ExceptionGroup'> m (2 sub-exceptions)
(ExceptionGroup('m', [TypeError(), PermissionError(1, 2)]), None) |
Ok, this is quite tricky. This function is firstly called with |
Has this been resolved by #9230, or is there more still to do? |
I think so! (I cannot close issues, please send help 😆 ) Thanks, @Zac-HD, for the report! |
Happy to help, and I will! |
In the following stubs, each use of
Self
in a return type is incorrect:typeshed/stdlib/builtins.pyi
Lines 1947 to 1959 in c626137
This is because
.derive()
must be manually overridden to return custom subtypes, in which case the child class should also manually update the type annotations on.subgroup()
and.split()
1. For example:Discovered via agronholm/exceptiongroup#40 (comment); if accepted here we should make sure to fix the backport too.
Footnotes
since I don't know of a way to express "that other method's return-type" in an annotation ↩
The text was updated successfully, but these errors were encountered: