Skip to content

Keep the parentheses around a right-hand side when a dict value's parentheses are made invisible - #5492

Merged
cobaltt7 merged 3 commits into
psf:mainfrom
jayzhou2309:fix/preview-dict-value-rhs-overflow
Oct 6, 2026
Merged

cobaltt7 merged 3 commits into
psf:mainfrom
jayzhou2309:fix/preview-dict-value-rhs-overflow

Conversation

@jayzhou2309

Copy link
Copy Markdown
Contributor

Description

Fixes #5488.

With --preview, this assignment loses the parentheses around its right-hand side and the first line grows to 96 characters:

self.a_long_class_attribute_to_use = A_LONG_TEMPLATE_CONSTANT_NAME_HERE.safe_substitute(
    {
        "n": (
            "long text that breaks the ternary" if A_LONG_CONSTANT_NAME else ""
        ),
    },
)

Stable style keeps the parentheses because of the second opinion in run_transformer: when the first line of a right-hand split is still too long, it retries with FORCE_OPTIONAL_PARENTHESES and takes that result if every line fits. The retry is skipped when one of the line's invisible parens has turned visible, because that means the split already used them.

wrap_long_dict_values_in_parens makes the dict value's own parens invisible, so they sit in the same bracket_tracker.invisible list as the line's optional parens. Splitting the sub-line for the dict value makes them visible again, the guard reads that as "the optional parens were used", and the retry never runs. The line's own optional parens are still invisible at that point.

Under wrap_long_dict_values_in_parens, the guard now looks only at the line's top-level invisible parens (bracket_depth == 0). The depths are read before the transform runs, because the sub-line splits re-mark the same leaves. Stable style takes the same code path as before, since the filter only applies with the preview feature on. The output now matches stable's shape:

self.a_long_class_attribute_to_use = (
    A_LONG_TEMPLATE_CONSTANT_NAME_HERE.safe_substitute(
        {
            "n": (
                "long text that breaks the ternary" if A_LONG_CONSTANT_NAME else ""
            ),
        },
    )
)

The existing Random.func case in preview_long_dict_values.py had the same bug: its expected output had a 90-character first line. It now wraps the right-hand side and fits in 88, so this PR updates that expectation.

Verification:

  • The new case in preview_long_dict_values.py fails on main and passes with the fix. It lands in its own commit before the fix.
  • The full test suite passes: 551 passed, 3 skipped. That includes preview_long_strings__regression, which the length-based approach discussed in the issue broke.
  • --preview output is byte-identical before and after the change across 2,268 files (installed site-packages, celery, agno, mempalace).
  • A second review ran 57 extra inputs (chained, annotated and augmented assignments, return, del, for, with, lambdas, comments, type: ignore) at line lengths 1, 40, 60, 88 and 120 in preview and unstable. None crashed, all were stable on a second pass, and none came out wider than on main.
  • pre-commit (isort, flake8, mypy, prettier) passes.

AI disclosure: an AI agent (Claude Code) reproduced the issue, traced the cause, wrote the fix, the test, and this description, and ran the checks above (pytest tests -n auto, pre-commit run, the corpus comparison against main).

Checklist - did you ...

  • Implement any code style changes under the --preview style, following the stability policy?
  • Add an entry in CHANGES.md if necessary?
  • Add / update tests if necessary?
  • Add new / update outdated documentation?

πŸ€– Written and posted by an AI agent (Claude Code) on behalf of @jayzhou2309.

jayzhou2309 and others added 2 commits October 6, 2026 21:25
…ible

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-split

The second-opinion retry with forced optional parens was skipped because
run_transformer saw a visible bracket among the line's invisible parens.
With wrap_long_dict_values_in_parens, that bracket can be a dict value's
paren made visible while splitting a sub-line, not the line's own optional
paren. Only the line's top-level optional parens now count.

Fixes psf#5488

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

diff-shades results comparing this PR (bbf7b32) to main (b801776):

--preview style: no changes

--stable style: no changes


What is this? | Workflow run | diff-shades documentation

Comment thread src/black/linegen.py Resolved
@jayzhou2309
jayzhou2309 force-pushed the fix/preview-dict-value-rhs-overflow branch from 29ee970 to a7d87b6 Compare October 6, 2026 17:08
Formatting 12,637 files from ten projects with stable style gives
byte-identical output with and without the preview gate. Of the 401
lines that reached the check with nested invisible parens, none had one
turned visible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jayzhou2309
jayzhou2309 force-pushed the fix/preview-dict-value-rhs-overflow branch from a7d87b6 to bbf7b32 Compare October 6, 2026 17:08
@cobaltt7

cobaltt7 commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

Thanks!

@cobaltt7
cobaltt7 merged commit 939fa93 into psf:main Oct 6, 2026
58 checks passed
luketainton pushed a commit to luketainton/luke_instant-msg-api that referenced this pull request Oct 10, 2026
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [black](https://github.com/psf/black) ([changelog](https://github.com/psf/black/blob/main/CHANGES.md)) | `<26.10.1,>=26.10.0` β†’ `<26.10.2,>=26.10.1` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/black/26.10.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/black/26.10.0/26.10.1?slim=true) |

---

### Release Notes

<details>
<summary>psf/black (black)</summary>

### [`v26.10.1`](https://github.com/psf/black/blob/HEAD/CHANGES.md#Version-26101)

[Compare Source](psf/black@26.10.0...26.10.1)

##### Highlights

This release fixes a security issue in Black's bundled GitHub Action. Repositories that
use the action are encouraged to update to the latest version of Black immediately. This
update is received automatically when using `psf/black@stable`, and is independent of
the version of Black installed by the action.

When the GitHub Action reads version specifiers from `tool.black.required-version`, it
now only accepts released versions of Black, as it already did for `black` requirements
in dependency lists. Other values, such as URL references, are now rejected. This issue
has been registered as `GHSA-cg8m-r9f2-5wm2`; a CVE identifier is pending.

##### Stable style

- Fix a long Jupyter notebook assignment magic (for example `x = !ls -la`) being wrapped
  in parentheses, which IPython can no longer run; such cells are now left unchanged
  ([#&#8203;5481](psf/black#5481))
- Keep repeated lines outside the selected `--line-ranges` unchanged ([#&#8203;5436](psf/black#5436))
- Fix `--line-ranges` formatting lines that follow a statement when formatting joins or
  re-indents that statement ([#&#8203;5484](psf/black#5484))
- Fix `--line-ranges` crashing with an internal error when an unselected statement has a
  `# fmt: skip` comment on the last line before a closing bracket ([#&#8203;5477](psf/black#5477))
- Prevent moving an encoding declaration (for example `# -*- coding: latin-1 -*-`) onto
  the first two lines of a file ([#&#8203;5487](psf/black#5487))
- Fix adding an extra trailing blank line inside a multi-line module docstring when its
  last line is near the line length limit ([#&#8203;5494](psf/black#5494))

##### Preview style

- Do not split short expressions on delimiters (such as binary operators, comparisons,
  or comprehensions) inside brackets when preceded by a standalone comment ([#&#8203;5455](psf/black#5455))
- Fix the unstable `string_processing` feature removing backslash-newline sequences from
  raw strings, which changed their value ([#&#8203;5482](psf/black#5482))
- Avoid adding unnecessary parentheses around unbreakable right-hand side expressions in
  assignments (such as annotated assignments or subscript targets) ([#&#8203;5473](psf/black#5473))
- Parenthesize expressions with parameter comments when they exceed the line length
  under `--preview` ([#&#8203;5442](psf/black#5442))
- Fix `wrap_long_dict_values_in_parens` dropping the parentheses around a right-hand
  side that contains a dictionary, leaving a line over the length limit ([#&#8203;5492](psf/black#5492))
- Fix the unstable `string_processing` feature adding a space after `*` or `**` in a PEP
  695 type parameter (`* Ts = ...`) when it splits a string in the parameter's default
  ([#&#8203;5504](psf/black#5504))
- Avoid unnecessary line splits of trailers (such as indexing/subscripts) when an
  earlier bracket split exceeds the line length only due to a trailing comment ([#&#8203;5498](psf/black#5498))

##### Configuration

- Add `--cache-dir` to configure the cache directory from the command line ([#&#8203;5433](psf/black#5433))
- Don't cache a file as formatted after formatting or checking it with `--line-ranges`,
  which made a later full `black --check` pass and `black` skip the unformatted lines
  ([#&#8203;5476](psf/black#5476))
- Respect a `.gitignore` that starts with a UTF-8 byte order mark. The mark became part
  of the first pattern, so that pattern never matched and Black formatted files that git
  ignores ([#&#8203;5497](psf/black#5497))

##### Performance

- Avoid quadratic runtime when splitting very long lines with many trailing bracket
  pairs (such as consecutive subscripts) ([#&#8203;5475](psf/black#5475))

##### Integrations

- Fix the GitHub Action crashing on dependency-group includes when `use_pyproject` is
  enabled ([#&#8203;5479](psf/black#5479))
- When the GitHub Action reads version specifiers from `tool.black.required-version`, it
  now only accepts released versions of Black, as it already did for `black`
  requirements in dependency lists. Other values, such as URL references, are now
  rejected. ([#&#8203;5510](psf/black#5510))

</details>

---

### Configuration

πŸ“… **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

β™» **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

πŸ”• **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xNDkuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjE0OS4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL2RlcGVuZGVuY2llcyJdfQ==-->

Reviewed-on: https://git.tainton.uk/luke/instant-msg-api/pulls/292
Co-authored-by: renovate[bot] <renovate-bot@git.tainton.uk>
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.

Black goes over line-length limit when wrapping long dicts in already long line

2 participants