Skip to content

Add Python 3.15 support / CI + fix B913 crash on unpack dict comprehensions - #580

Merged
cooperlees merged 2 commits into
mainfrom
add-py315-ci
Sep 28, 2026
Merged

cooperlees merged 2 commits into
mainfrom
add-py315-ci

Conversation

@cooperlees

Copy link
Copy Markdown
Collaborator

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

Adds 3.15 to the CI matrix (ci.yml), tox envs (py315 + gh-actions mapping + mypy env), the trove classifier, and DEVELOPMENT.md. 3.15 final isn't released yet, so local testing was done against python:3.15-rc (3.15.0rc2) via Docker.

Testing found one real 3.15 bug, fixed here:

  • B913 crash: Python 3.15 allows {**d for d in dicts} in dict comprehensions, making ast.DictComp.value None. B913UsageFinder.visit_DictComp passed [node.key, node.value] into visit(), crashing with AttributeError: 'NoneType' object has no attribute '_fields'. Reproduced end-to-end (a zip() loop with a single _ target whose body contains an unpack-dictcomp) — now guarded, with regression test tests/eval_files/b913_py315.py (fails without the fix, passes with it).
  • mypy: main shows 4 pre-existing errors on 3.14; 3.15 added a 5th at the same line (List item 1 has incompatible type "expr | None"). The fix resolves it — back to the same 4.

Results:

  • Docker python:3.15-rc: 85 passed, flake8 selfclean clean
  • Local 3.14 venv: 84 passed, 1 skipped (new py315 eval file correctly gated by the _pyXX convention)

Also adds README UNRELEASED changelog entries.

…nsions

- CI matrix, tox envs, trove classifier, DEVELOPMENT.md: add 3.15
- B913UsageFinder.visit_DictComp: DictComp.value is None for
  '{**d for d in ...}' on 3.15+, guard it (also fixes the new mypy
  error); regression test in tests/eval_files/b913_py315.py
- README UNRELEASED changelog entries

Test:
- Full suite in docker python:3.15-rc (3.15.0rc2): 85 passed;
  flake8 selfclean clean; mypy shows only the 4 pre-existing errors
- Local 3.14 venv: 84 passed, 1 skipped (new py315 eval file)
@cooperlees

Copy link
Copy Markdown
Collaborator Author

This review is from Claude (Claude Code), posting on behalf of @cooperlees.

Overall this looks good to merge. The CI matrix, tox, the classifier and the docs are all consistent. allow-prereleases: true is already set in ci.yml, so the 3.15 job resolves to the RC, and all jobs are green.

Verified locally with python:3.15-rc (3.15.0rc2):

  • ast.dump(ast.parse("{**d for d in x}")) gives DictComp(key=Name(id='d'), generators=[...]) with no value. That confirms the premise of the B913 fix, and the guard in B913UsageFinder.visit_DictComp handles it correctly.
  • The new b913_py315.py eval file is gated by the _pyXX convention as intended.

Non-blocking: B035 on unpack dict comprehensions
BugBearVisitor.visit_DictComp → check_for_b035 doesn't crash on the new syntax, but it now treats the unpacked expression as a "key":

base = {}
b = {**base for _ in range(3)}
# 3.15: B035 Static key in dict comprehension 'base'.

base isn't a key here, so the message is misleading. You could argue that code is pointless anyway, but it's better to skip B035 when node.value is None (or give it a dedicated message). Fine as a follow-up PR.

Nits

  • tox.ini envlist still starts with py39, but we only support 3.10+. It's worth dropping while that line is being edited, which also brings it in line with the "3.10 through 3.15" wording in DEVELOPMENT.md.
  • 3.15.0 final isn't out yet. The classifier is fine to merge now, but it's probably best to hold the release that advertises 3.15 until final is out, or at least to re-run CI against it.

LGTM from me with the above as optional follow-ups.

- tox.ini: drop stale py39 from envlist (3.10+ only)
- B035: skip when DictComp.value is None (3.15 unpack), the key field
  holds the unpacked expression, not a static key; regression test in
  tests/eval_files/b035_py315.py

Test: 3.15-rc docker: 86 passed; local 3.14 venv: 84 passed, 2 skipped
@cooperlees

Copy link
Copy Markdown
Collaborator Author

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

Thanks for the review — both nits fixed in d68a311 (release-timing point noted but intentionally left as-is; we'll keep re-running CI against newer 3.15 pre-releases as they come out):

  • py39 envlist: dropped — line now reads py310 … py315, pep8_naming, mypy, matching the 3.10+ floor and the DEVELOPMENT.md wording.
  • B035 on unpack dictcomps: fixed per your suggestion — check_for_b035 returns early when node.value is None, since the key field holds the unpacked expression, not a key. Regression test in tests/eval_files/b035_py315.py: verified it fails without the fix (reports B035 Static key … 'base' on {**base for _ in range(3)}) and passes with it. Changelog entry updated.

Verified: 3.15-rc Docker 86 passed, flake8 selfclean clean; local 3.14 venv 84 passed, 2 skipped (both py315 files gated).

@cooperlees
cooperlees merged commit d64ab05 into main Sep 28, 2026
13 checks passed
@cooperlees
cooperlees deleted the add-py315-ci branch September 28, 2026 15:15
cooperlees added a commit to aashish254/flake8-bugbear that referenced this pull request Sep 28, 2026
Keep B020 (PyCQA#521) entry alongside the 3.15/B913/B035 entries (PyCQA#580).
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.

1 participant