Skip to content

B042: don't count a parameterized generic base as an exception super class - #578

Merged
cooperlees merged 1 commit into
PyCQA:mainfrom
Eljees:b042-generic-alias-base
Sep 27, 2026
Merged

cooperlees merged 1 commit into
PyCQA:mainfrom
Eljees:b042-generic-alias-base

Conversation

@Eljees

@Eljees Eljees commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Refs #538.

B042 treats a class as an exception when its name looks like one and it has any base at all, so class FailOnException(ContextManager[T]) from the issue is checked like an exception class. Resolving real inheritance from BaseException is out of reach for a single-file check, so this takes the narrow, syntactic part of it: a parameterized generic base, such as ContextManager[T] or typing.Iterator[int], no longer counts as the super class that makes a class named ...Exception or ...Error an exception. A generic base whose own name looks like an exception, such as MyError[int], is now recognised, which it wasn't before because only plain names were checked.

Everything else is unchanged. A class named like an exception with any non-generic base is still checked, since that base may be an exception under another name, and bases named like exceptions are matched as before.

New cases in tests/eval_files/b042.py:

  • FailOnException(typing.ContextManager[int]) and FailOnError(typing.Iterator[int]): no longer reported. Both are reported on main;
  • MyGenericError(typing.Iterator[int], Anything): still reported, through the non-generic base;
  • Anything5(MyError[int]): now reported. main misses it.

On main, the eval test fails on exactly these lines. With the change, pytest tests: 81 passed, 2 skipped. There is a changelog entry under UNRELEASED.

…class

A class named like an exception whose only bases are generics such as ContextManager[T] is not an exception. A generic base named like an exception, such as MyError[int], now counts. Refs PyCQA#538.
@cooperlees
cooperlees requested a lite review from Copilot September 27, 2026 20:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@cooperlees cooperlees left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is muse (muse-spark, posting on behalf of @cooperlees via his coding agent).

Reviewed the diff and verified locally: checked out the PR head (a0859da) and ran the full suite in an isolated venv in /tmp — 83 passed, including the new b042.py eval cases.

What I checked:

  • FailOnException(typing.ContextManager[int]) / FailOnError(typing.Iterator[int]): correctly no longer flagged — the only bases are ast.Subscript, so concrete_bases is empty and the unwrapped Attribute base doesn't match the exception-name heuristic. This fixes the false positive in #538.
  • MyGenericError(typing.Iterator[int], Anything): still flagged via the non-generic base — good, preserves the "base may be an exception under another name" behavior.
  • Anything5(MyError[int]): now flagged by unwrapping the Subscript to Name(MyError) — a genuine improvement over main.
  • Plain cases (class FooError(Exception), bare-name bases) are untouched; the change is narrowly scoped to ast.Subscript bases.

One non-blocking observation: an attribute-style generic base like typing.MyError[int] still won't match since only ast.Name is checked after unwrapping — but that's a pre-existing gap (non-subscripted Attribute bases were never matched either), so keeping this PR narrow is the right call.

Changelog entry under UNRELEASED looks good. Approving.

@cooperlees

Copy link
Copy Markdown
Collaborator

Many thanks !

@cooperlees
cooperlees merged commit 4c3f7b6 into PyCQA:main Sep 27, 2026
6 checks passed
cooperlees added a commit to Eljees/flake8-bugbear that referenced this pull request Sep 27, 2026
Keep both B018 (PyCQA#452) and B042 (PyCQA#538) entries (PR PyCQA#578 merged first).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants