Skip to content

don't escape nested quotes inside t-string replacement fields - #5265

Merged
cobaltt7 merged 1 commit into
psf:mainfrom
sahvx655-wq:tstring-nested-quotes
Jul 28, 2026
Merged

don't escape nested quotes inside t-string replacement fields#5265
cobaltt7 merged 1 commit into
psf:mainfrom
sahvx655-wq:tstring-nested-quotes

Conversation

@sahvx655-wq

Copy link
Copy Markdown
Contributor

Description

Black emits a t-string it cannot parse when a replacement field contains a quote. I came
at this from the f-string side, checking that t-strings had inherited the same
protections, and they had not: visit_tstring tests child.type == syms.fstring_replacement_field, but a tstring node's children are
tstring_replacement_field (symbol 356 against 299), so that bail-out has never fired
for a single t-string. normalize_string_quotes then gates its "do not introduce
backslashes in interpolated expressions" check on "f" in prefix.casefold(), which is
false for t, rt and tr. With both guards bypassed, t'\'{a["b"]}\'' normalises to
t"'{a[\"b\"]}'" and the forced second pass falls over on its own output with
InvalidInput: Cannot parse: 1:9 ... TokenError: Failed to parse: UnexpectedCharacterAfterBackslash. One such literal makes a whole file unformattable and
blackd answers 400 for valid input, so it seems worth closing before 3.14 t-strings
turn up in real code.

Both checks belong where they already sit rather than at the call site, since the caller
only has the finished literal and cannot tell a quote inside a replacement field from one
in the literal part. They are not redundant either: the visit_tstring symbol covers a
field that already carries a backslash, while the prefix check catches the backslashes
normalisation itself introduces, and reverting either one puts the committed case back
into failure (the first with a diff, the second with the parse error). On behaviour, I
compared 59k formatting results across a synthetic prefix/quote matrix and the repo's own
sources in stable and preview: 10 crashes fixed, nothing newly broken, and no literal
without a t prefix moves at all. What does move is t-strings carrying a backslash in a
replacement field, which are now left alone instead of being requoted; that is the
f-string behaviour they should have had from the start, and 20736 f/t pairs across raw
and non-raw prefixes now agree exactly. I read that as part of the same defect rather
than a style change, but say the word if you would rather see it split.

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?

@github-actions

Copy link
Copy Markdown
Contributor

diff-shades results comparing this PR (c2d12ec) to main (ba7debf):

--preview style: no changes

--stable style: no changes


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

@cobaltt7
cobaltt7 merged commit c7801d9 into psf:main Jul 28, 2026
57 checks passed
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.

2 participants