Skip to content

Hug closing } when f-string expression has a format specifier #18704

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 17, 2025

Conversation

MichaReiser
Copy link
Member

@MichaReiser MichaReiser commented Jun 16, 2025

Summary

This PR changes how we format f-string expressions with a format specifier.

Before:

  • triple-quoted f- or t-strings with a format specifier were always formatted onto a single line
  • single-quoted f- or t-strings could break over multiple lines even when they had format specifiers. The format specifier was separated by a newline + indent from the closing }. This is now invalid syntax under Python 3.13.4.
    f"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
        a:.3f
    }moreeeeeeeeeeeeeeeeeetest"  

This PR changes two address the invalid syntax and make single and triple quoted strings more consisent:

  • f- or t-string elements with a format specifier now hug the closing }. This ensures that the formatter doesn't introduce a now invalid line break after the format specifier.

    f"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
        a:.3f}moreeeeeeeeeeeeeeeeeetest"  
  • Remove the restriction that triple-quoted f- or t-strings can't use the multiline layout if they have a format specifier. This isn't required to fix the invalid syntax BUT it fixes an issue where the formatter requires two passes to correctly format comments nested inside an interpolated format specifier:

    f"""{foo
       :a{
         a # more
        # comment
       }
    }"""

    which before this PR gets formatted to:

    f"""{foo:a{a}
    }"""  # more
        # comment

    which is fairly different from the source. I think making this change should be fine, if this PR makes it into the minor release tomorrow.

I considered if we should always keep elements with a format specifier flat. However, this gets very tricky if the format specifier is already formatted over multiple lines and contains comments (see the triple-quoted case above). That's why I opted to allow them and making the behavior consistent between single and triple quoted strings.

Fixes #18672
Fixes #18632

Note, this PR doesn't change the parser / lexer

Test Plan

Updated / added tests

@MichaReiser MichaReiser added bug Something isn't working formatter Related to the formatter labels Jun 16, 2025
@@ -304,14 +304,14 @@
"""

# Mix of various features.
f"{ # comment 26
f"""{ # comment 26
Copy link
Member Author

Choose a reason for hiding this comment

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

This example is a syntax error for a single quoted string, but it's perfectly fine for a triple quoted string

Copy link
Contributor

github-actions bot commented Jun 16, 2025

ruff-ecosystem results

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

@MichaReiser MichaReiser force-pushed the micha/f-string-format-spec branch from 4e8bbad to 5056b8c Compare June 16, 2025 11:39
@MichaReiser MichaReiser force-pushed the micha/f-string-format-spec branch from 5056b8c to be72aa7 Compare June 16, 2025 12:18
@MichaReiser MichaReiser changed the title Don't use soft_block indent for f-strings with a foramt specifier Don't use soft_block indent for f-strings with a format specifier Jun 16, 2025
@MichaReiser MichaReiser changed the title Don't use soft_block indent for f-strings with a format specifier Hug closing } when f-string expression has a format specifier Jun 16, 2025
@MichaReiser MichaReiser requested a review from dhruvmanila June 16, 2025 14:23
@MichaReiser MichaReiser marked this pull request as ready for review June 16, 2025 14:23
Copy link
Contributor

github-actions bot commented Jun 16, 2025

mypy_primer results

No ecosystem changes detected ✅

Copy link
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.

This makes sense to me, for what it's worth. It definitely makes sense to wait for Dhruv's review, just wanted to take a quick look too in case it helped.

if conversion.is_none() && format_spec.is_none() {
bracket_spacing.fmt(f)?;
} else if comments.has_trailing_own_line(self.element) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this empty branch part of the TODO above?

Copy link
Member Author

Choose a reason for hiding this comment

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

Lol no, that's just me moving stuff around and forgetting to delete it (and clippy not telling me)

@MichaReiser
Copy link
Member Author

@ntBre for the changelog (assuming this makes it in). We could probably write something like this

Ruff now formats multiline f-strings with format-specifiers differently because of a change to CPython's grammar. Before, Ruff formatted the interpolation expression like so:

f"This is some long string {
    x:d
} that is formatted across multiple lines"  

The line break after the :d format specifier is now a syntax error with Python 3.13.4 or newer. That's why Ruff now formats the f-string like so:

f"This is some long string {
    x:d} that is formatted across multiple lines"  

@ntBre
Copy link
Contributor

ntBre commented Jun 16, 2025

Thanks! I'll add it to the blog post too. There's no real rush on getting the release out from my side, so I think we can wait until this is ready.

@ntBre ntBre mentioned this pull request Jun 16, 2025
2 tasks
@ntBre ntBre added this to the v0.12 milestone Jun 16, 2025
Copy link
Member

@dhruvmanila dhruvmanila left a comment

Choose a reason for hiding this comment

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

Looks great! Thank you for the quick fix!

Comment on lines +262 to +270
// TODO: The `or comments.has_trailing...` can be removed once newlines in format specs are a syntax error.
// This is to support the following case:
// ```py
// x = f"{x !s
// :>0
// # comment 21
// }"
// ```
if format_spec.is_none() || comments.has_trailing_own_line(self.element) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm guessing the example mentioned in the comment is specifically to target the format_spec.is_none case and not the trailing comment case. And, as the latter case is going to be removed, we don't really need to provide an example for that here.

Copy link
Member Author

Choose a reason for hiding this comment

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

The example is for the format_spec.is_some case. It will be removed in my next PR.

I mainly provided it because I found it helpful to reason about all the changes i've been making. I think I keep it, considering that I already have a PR that removes it again.

@dhruvmanila
Copy link
Member

dhruvmanila commented Jun 17, 2025

  • Remove the restriction that triple-quoted f- or t-strings can't use the multiline layout if they have a format specifier. This isn't required to fix the invalid syntax BUT it fixes an issue where the formatter requires two passes to correctly format comments nested inside an interpolated format specifier:

Yeah, I think I mainly did this because the newline for a triple-quoted f-string is a newline character in the literal string and not an actual newline, so that didn't account for the fact that the f-string is multiline. For example, the following is not a multiline f-string:

aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd:.3f
} cccccccccc"""

But, it does create inconsistent behavior between triple-quoted and single-quoted f-string which you've noticed. One might argue that is not really an inconsistent behavior as it does follow what we stated regarding when the expression could be broken down i.e., only when the user already has a multiline f-string.

In reality, I guess this shouldn't affect too many users as newlines in format spec should be rare enough especially now that it's a syntax error. So, I'm fine with this change.

@MichaReiser
Copy link
Member Author

Yeah, I think I mainly did this because the newline for a triple-quoted f-string is a newline character in the literal string and not an actual newline, so that didn't account for the fact that the f-string is multiline. For example, the following is not a multiline f-string:

I learned this the hard way when working on this PR 😅. Thanks for adding tests for it.

In reality, I guess this should affect too many users as newlines in format spec should be rare enough especially now that it's a syntax error. So, I'm fine with this change.

Yeah, that was my thinking too and seems to be confirmed by our ecosyste checks.

@MichaReiser
Copy link
Member Author

@ntBre I'll merge this to main, considering that we plan to do the minor release today.

@MichaReiser MichaReiser merged commit c22f809 into main Jun 17, 2025
34 checks passed
@MichaReiser MichaReiser deleted the micha/f-string-format-spec branch June 17, 2025 05:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working formatter Related to the formatter
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Formatter can delete comments between f-string expr and format spec Formatting produces invalid code on 3.13.4
3 participants