⚡ Sangsakala Proxy 🏠 Home
Skip to content

ExceptionGroup.subgroup() returns a fully matching nested subclass group unchanged instead of calling derive() #9576

Open

Description

@taxor03

Summary

subgroup() returns a nested exception group subclass as the same object when all of its leaves match, without calling derive(). split() does call derive(), so eg.split(c)[0] and eg.subgroup(c) disagree. The docs for derive() say: "This method is used by subgroup() and split(), which are used in various contexts to break up an exception group. A subclass needs to override it in order to make subgroup() and split() return instances of the subclass rather than ExceptionGroup."

Reproducer

class MyEG(ExceptionGroup):
    pass
inner = MyEG("inner", [ValueError(1)])
eg = ExceptionGroup("outer", [inner, TypeError(2)])
s = eg.subgroup(ValueError)
print("subgroup:", type(s.exceptions[0]).__name__, s.exceptions[0] is inner)
m, r = eg.split(ValueError)
print("split:   ", type(m.exceptions[0]).__name__, m.exceptions[0] is inner)
print("top:     ", type(inner.subgroup(ValueError)).__name__)

Expected

CPython 3.15.2 (same on 3.12.16):

subgroup: ExceptionGroup False
split:    ExceptionGroup False
top:      ExceptionGroup

Actual

RustPython main (ef023bc):

subgroup: MyEG True
split:    ExceptionGroup False
top:      MyEG

Cause

subgroup() tracks whether anything was dropped and returns zelf when nothing was (if !modified { return Ok(zelf.into()); }), so a group whose leaves all match is never passed to derive() (exception_group.rs#L126-L161). split() right below has no such shortcut. CPython only returns the group itself when the condition matches the group as a whole; otherwise it rebuilds every partially or fully matching nested group with derive(), for split() and subgroup() alike (exceptions.c#L1067-L1100, #L1154-L1157, #L1209-L1233). Dropping the modified shortcut and always calling derive_and_copy_attributes when matching is non-empty would match CPython. PyPy had the same shortcut and just removed it this way (pypy/pypy#5717).

test_exception_group.NestedExceptionGroupSubclassSplitTest.test_split_BaseExceptionGroup_subclass_no_derive_new_override passes because for subgroup() it only checks the leaves.

Drafted with help from Claude Code; I reviewed and checked it before posting.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions