[`ruff`] Fix UP032 conversion for decimal ints with underscores (#21022)

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

Fixes #21017

Taught UP032’s parenthesize check to ignore underscores when inspecting
decimal integer literals so the converter emits `f"{(1_2).real}"`
instead of invalid syntax.

## Test Plan

Added test cases to UP032_2.py.

<!-- How was it tested? -->
This commit is contained in:
Takayuki Maeda 2025-10-23 07:11:50 +09:00 committed by GitHub
parent 7ba176d395
commit 6c18f18450
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 68 additions and 1 deletions

View File

@ -26,3 +26,8 @@
"{.real}".format({1, 2}) "{.real}".format({1, 2})
"{.real}".format({1: 2, 3: 4}) "{.real}".format({1: 2, 3: 4})
"{}".format((i for i in range(2))) "{}".format((i for i in range(2)))
# https://github.com/astral-sh/ruff/issues/21017
"{.real}".format(1_2)
"{0.real}".format(1_2)
"{a.real}".format(a=1_2)

View File

@ -160,7 +160,11 @@ fn parenthesize(expr: &Expr, text: &str, context: FormatContext) -> bool {
value: ast::Number::Int(..), value: ast::Number::Int(..),
.. ..
}), }),
) => text.chars().all(|c| c.is_ascii_digit()), ) => text
.chars()
// Ignore digit separators so decimal literals like `1_2` still count as pure digits.
.filter(|c| *c != '_')
.all(|c| c.is_ascii_digit()),
// E.g., `{x, y}` should be parenthesized in `f"{(x, y)}"`. // E.g., `{x, y}` should be parenthesized in `f"{(x, y)}"`.
( (
_, _,

View File

@ -385,6 +385,7 @@ help: Convert to f-string
26 + f"{({1, 2}).real}" 26 + f"{({1, 2}).real}"
27 | "{.real}".format({1: 2, 3: 4}) 27 | "{.real}".format({1: 2, 3: 4})
28 | "{}".format((i for i in range(2))) 28 | "{}".format((i for i in range(2)))
29 |
UP032 [*] Use f-string instead of `format` call UP032 [*] Use f-string instead of `format` call
--> UP032_2.py:27:1 --> UP032_2.py:27:1
@ -402,6 +403,8 @@ help: Convert to f-string
- "{.real}".format({1: 2, 3: 4}) - "{.real}".format({1: 2, 3: 4})
27 + f"{({1: 2, 3: 4}).real}" 27 + f"{({1: 2, 3: 4}).real}"
28 | "{}".format((i for i in range(2))) 28 | "{}".format((i for i in range(2)))
29 |
30 | # https://github.com/astral-sh/ruff/issues/21017
UP032 [*] Use f-string instead of `format` call UP032 [*] Use f-string instead of `format` call
--> UP032_2.py:28:1 --> UP032_2.py:28:1
@ -410,6 +413,8 @@ UP032 [*] Use f-string instead of `format` call
27 | "{.real}".format({1: 2, 3: 4}) 27 | "{.real}".format({1: 2, 3: 4})
28 | "{}".format((i for i in range(2))) 28 | "{}".format((i for i in range(2)))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29 |
30 | # https://github.com/astral-sh/ruff/issues/21017
| |
help: Convert to f-string help: Convert to f-string
25 | "{.real}".format([1, 2]) 25 | "{.real}".format([1, 2])
@ -417,3 +422,56 @@ help: Convert to f-string
27 | "{.real}".format({1: 2, 3: 4}) 27 | "{.real}".format({1: 2, 3: 4})
- "{}".format((i for i in range(2))) - "{}".format((i for i in range(2)))
28 + f"{(i for i in range(2))}" 28 + f"{(i for i in range(2))}"
29 |
30 | # https://github.com/astral-sh/ruff/issues/21017
31 | "{.real}".format(1_2)
UP032 [*] Use f-string instead of `format` call
--> UP032_2.py:31:1
|
30 | # https://github.com/astral-sh/ruff/issues/21017
31 | "{.real}".format(1_2)
| ^^^^^^^^^^^^^^^^^^^^^
32 | "{0.real}".format(1_2)
33 | "{a.real}".format(a=1_2)
|
help: Convert to f-string
28 | "{}".format((i for i in range(2)))
29 |
30 | # https://github.com/astral-sh/ruff/issues/21017
- "{.real}".format(1_2)
31 + f"{(1_2).real}"
32 | "{0.real}".format(1_2)
33 | "{a.real}".format(a=1_2)
UP032 [*] Use f-string instead of `format` call
--> UP032_2.py:32:1
|
30 | # https://github.com/astral-sh/ruff/issues/21017
31 | "{.real}".format(1_2)
32 | "{0.real}".format(1_2)
| ^^^^^^^^^^^^^^^^^^^^^^
33 | "{a.real}".format(a=1_2)
|
help: Convert to f-string
29 |
30 | # https://github.com/astral-sh/ruff/issues/21017
31 | "{.real}".format(1_2)
- "{0.real}".format(1_2)
32 + f"{(1_2).real}"
33 | "{a.real}".format(a=1_2)
UP032 [*] Use f-string instead of `format` call
--> UP032_2.py:33:1
|
31 | "{.real}".format(1_2)
32 | "{0.real}".format(1_2)
33 | "{a.real}".format(a=1_2)
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to f-string
30 | # https://github.com/astral-sh/ruff/issues/21017
31 | "{.real}".format(1_2)
32 | "{0.real}".format(1_2)
- "{a.real}".format(a=1_2)
33 + f"{(1_2).real}"