Skip to content

Suppress PLE2510 fix inside f-string interpolation for Python < 3.12#23910

Closed
omar-y-abdi wants to merge 3 commits into
astral-sh:mainfrom
omar-y-abdi:fix/18816-ple2510-fstring-syntax-error
Closed

Suppress PLE2510 fix inside f-string interpolation for Python < 3.12#23910
omar-y-abdi wants to merge 3 commits into
astral-sh:mainfrom
omar-y-abdi:fix/18816-ple2510-fstring-syntax-error

Conversation

@omar-y-abdi
Copy link
Copy Markdown

Summary

Fixes #18816.

The PLE2510/PLE2512/PLE2513/PLE2514/PLE2515 autofix replaces raw control characters with escape sequences (e.g., \x08 byte with \b). When the control character is inside an f-string interpolation expression, this introduces a backslash inside {...}, which is a syntax error in Python < 3.12 (before PEP 701).

This PR suppresses the autofix (not the diagnostic) when the token is inside an f-string/t-string interpolation and the target version doesn't support PEP 701.

Approach

Track f-string/t-string interpolation nesting in the token loop using a brace-depth stack (Vec<u32>):

  • FStringStart/TStringStart pushes a new depth counter
  • Lbrace increments the current depth (handles nested dict/set literals)
  • Rbrace decrements the current depth
  • FStringEnd/TStringEnd pops the stack
  • depth > 0 means we're inside an interpolation {...}

The in_interpolation flag and resolved target_version are passed to invalid_string_characters, which suppresses the fix when in_interpolation && !target_version.supports_pep_701().

Test plan

  • Added invalid_characters_fstring.py fixture with control characters in:
    • String literal inside f-string interpolation
    • Format spec inside interpolation
    • F-string literal part (not interpolation)
    • Regular string
  • invalid_characters_fstring_py39 test: verifies fix is suppressed for interpolation cases
  • invalid_characters_fstring_py312 test: verifies fix is applied for all cases
  • All existing tests pass unchanged

astral-sh#18816 caused the PLE2510 autofix to introduce syntax errors when
replacing control characters with escape sequences inside f-string
interpolation expressions. In Python < 3.12, backslash escapes are
not allowed inside f-string `{...}` (PEP 701 lifted this in 3.12).

Fixed by tracking f-string/t-string interpolation nesting via a
brace-depth stack in the token loop, then suppressing the fix (not
the diagnostic) when the token is inside an interpolation and the
target version doesn't support PEP 701.

Closes astral-sh#18816
@astral-sh-bot astral-sh-bot Bot requested a review from ntBre March 12, 2026 08:56
Copy link
Copy Markdown
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

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

Thanks for working on this! The changes look good to me overall, I just had one minor suggestion for testing and an idea for allowing escapes in format specs like CPython does.

Comment thread crates/ruff_linter/src/rules/pylint/mod.rs
@ntBre ntBre added bug Something isn't working fixes Related to suggested fixes for violations labels Mar 12, 2026
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot Bot commented Mar 12, 2026

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Address review feedback from ntBre:

- Allow autofix for FStringMiddle/TStringMiddle tokens inside
  interpolation: format specs support backslash escapes in all
  Python versions, only expression bodies are restricted pre-3.12.

- Replace two separate snapshot tests (py39/py312) with a single
  assert_diagnostics_diff! call since they only differ by target
  version.

- Move interpolation/version logic to the call site in tokens.rs
  so invalid_string_characters receives a single suppress_fix bool
  instead of in_interpolation + target_version.

- Remove redundant if-let after is_empty guard, skip function call
  for non-string tokens.
@omar-y-abdi
Copy link
Copy Markdown
Author

Thanks for the review! Addressed both suggestions:

  1. Format specs: Backslash escapes in format specs are now allowed for all Python versions. The FStringMiddle/TStringMiddle check is done at the call site so in_interpolation is only true for actual expression bodies.

  2. assert_diagnostics_diff!: Replaced the two separate tests with a single diff test. The snapshot now clearly shows the only behavioral difference between py39 and py312 is the fix on line 6 (string inside interpolation expression).

Also simplified the function signature -- invalid_string_characters now takes a single suppress_fix: bool instead of in_interpolation + target_version, and non-string tokens skip the call entirely.

@omar-y-abdi omar-y-abdi requested a review from ntBre March 12, 2026 17:09
Copy link
Copy Markdown
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

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

Thanks, the tests look good now, but I'm a bit confused by some of the other changes in the last commit.

Comment thread crates/ruff_linter/src/checkers/tokens.rs Outdated
Comment thread crates/ruff_linter/src/checkers/tokens.rs Outdated
@omar-y-abdi
Copy link
Copy Markdown
Author

@ntBre Hey, regarding your comments:
Both are reverted in the latest commit.

The unwrap change and the matches move were unnecessary refactoring that you never asked for in your first round of feedback.

I restored the if let and moved the matches call back into the rule function.

@omar-y-abdi omar-y-abdi requested a review from ntBre March 14, 2026 14:23
@ntBre ntBre closed this May 8, 2026
@ntBre ntBre reopened this May 8, 2026
@ntBre
Copy link
Copy Markdown
Contributor

ntBre commented May 11, 2026

@omar-y-abdi are you interested in resolving the CI failures? I think you just need to merge the main branch and then update the snapshots because of a context window change in our output formatting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working fixes Related to suggested fixes for violations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Pylint] PLE2510 fix causes syntax error inside pre-3.12 f-strings

3 participants