Skip to content

Fix: classify.is_f_string()'s pre-3.12 fallback (added in PR #330) flags ANY... - #376

Open
M001N wants to merge 2 commits into
PyCQA:masterfrom
M001N:oss-engine/223b1c66-905b8619
Open

Fix: classify.is_f_string()'s pre-3.12 fallback (added in PR #330) flags ANY...#376
M001N wants to merge 2 commits into
PyCQA:masterfrom
M001N:oss-engine/223b1c66-905b8619

Conversation

@M001N

@M001N M001N commented Aug 16, 2026

Copy link
Copy Markdown

Summary

Rewrote is_f_string() so the pre-3.12 branch unconditionally returns False (with an explanatory comment) instead of doing the unguarded f"""/f''' string-prefix match. On Python <3.12 there is never a legitimate case where this row-stitching should fire, since f-strings aren't split into multiple tokens by that version's tokenizer.

Problem

PyCQA/docformatter issue reference: #367

Root Cause

classify.is_f_string()'s pre-3.12 fallback (added in PR #330) flags ANY token whose string starts with f"""/f''' as an f-string, with no bracket or assignment-context check. This predicate is consumed generically in format.py's _get_unmatched_start_end_indices(), which uses it to decide whether a token's start row should be pinned to the previous token's end row instead of incrementing normally -- a stitching fix-up that is only meaningful for Python 3.12+'s PEP 701 tokenizer, which splits an f-string into FSTRING_START/MIDDLE/END tokens that need to be glued back onto one row. On Python <3.12 an f-string is always a single STRING token, so this stitching never applies, but the naive prefix check made it fire anyway for the multi-line f-string nested inside return (f"""...""",), corrupting that token's row bookkeeping and cascading into the untokenize ValueError. This is unrelated to the #364 _is_inside_brackets guard in is_attribute_docstring, which is never reached here because there is no '=' before the string in the repro -- confirming the previous attempt's root-cause claim was wrong, as the reviewer found.

Testing

PASS: on Python 3.11.9 the issue_367 test now passes (previously crashed with the reported ValueError, verified both before and after the fix); full 3.11 suite shows 515 passed vs. only the pre-existing, fix-unrelated 'preserve_line_ending' failure and 36 VIRTUAL_ENV-dependent CLI-subprocess errors (both reproduced identically on unmodified HEAD, confirming they are environment limitations, not regressions). Python 3.14 full suite: 516 passed with the same single pre-existing failure.

Related Issue

#367

dataflow-solutions-sk added 2 commits August 16, 2026 17:00
…Python <3.12 (PyCQA#367)

is_f_string()'s pre-3.12 fallback (from PyCQA#330) matched any token whose
string started with f"""/f''' as an f-string, regardless of bracket or
assignment context. That flag was consumed generically by
_get_unmatched_start_end_indices() to decide whether a token's start row
should be pinned to the previous token's end row -- a fix-up that only
makes sense for PEP 701's split FSTRING_START/MIDDLE/END token sequence
(Python 3.12+).

Before 3.12, an f-string is always a single STRING token, so there is
nothing to stitch together and the check should never fire. Because it
fired anyway, a multi-line f-string nested inside a parenthesized
expression (e.g. `return (f"""...""",)`) had its row bookkeeping
corrupted, causing tokenize.untokenize() to raise
"ValueError: start precedes previous end" on Python 3.10/3.11 -- the
crash reported in PyCQA#367. (This is unrelated to the PyCQA#364 bracket guard in
is_attribute_docstring/_is_inside_brackets, which is never reached here
since there is no preceding "=".)

Verified the crash and the fix directly against Python 3.11.9 (embeddable
distribution) since this environment only ships Python 3.12/3.14:
  - Pre-fix: tests/formatter/test_do_format_code.py::issue_367 raised
    ValueError: start (2,8) precedes previous end (3,4) on 3.11.9.
  - Post-fix: the same test passes on 3.11.9, and the full suite shows no
    new failures (the lone "preserve_line_ending" failure and the
    VIRTUAL_ENV-dependent end-to-end errors are pre-existing environment
    limitations, reproduced identically before this change).

The regression test added in a prior commit (using the exact issue PyCQA#367
reproducer) now actually exercises the buggy pre-3.12 code path.
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