Skip to content

Fix invalid IR when flattening nested BestFitting elements#25398

Merged
ntBre merged 3 commits into
mainfrom
brent/lambda-group
May 26, 2026
Merged

Fix invalid IR when flattening nested BestFitting elements#25398
ntBre merged 3 commits into
mainfrom
brent/lambda-group

Conversation

@ntBre
Copy link
Copy Markdown
Contributor

@ntBre ntBre commented May 26, 2026

Summary

This PR fixes #25397 by dropping StartBestFittingEntry and EndBestFittingEntry tags in
RemoveSoftLinesBuffer. #25144 changed RemoveSoftLinesBuffer to inline the most-flat variant of
BestFitting elements, but it inlined them with their start and end tags, which caused the problem
observed in #25397 when these tags became nested in another BestFitting element, which interpreted
the first End tag as ending the outer BestFitting rather than the nested one. You can see this
clearly in the IR. First, the diff between the 0.15.13 and 0.15.14 IR, which reveals the unmatched
start tags:

0.15.13 vs main
@@ -38,29 +38,13 @@
                           "lambda ",
                           group(["arg"]),
                           ": ",
-                          best_fitting([
-                            [
-                              [
-                                <interned 1> [
-                                  "str(",
-                                  group([
-                                    indent([soft_line_break, group(["arg"])]),
-                                    soft_line_break
-                                  ]),
-                                  ")"
-                                ]
-                              ]
-                            ]
-                            [[group(expand: true, [<ref interned *1>])]]
-                            [
-                              [
-                                "(",
-                                indent([hard_line_break, <ref interned *1>]),
-                                hard_line_break,
-                                ")"
-                              ]
+                          [
+                            <interned 1> [
+                              "str(",
+                              group([indent([group(["arg"])])]),
+                              ")"
                             ]
-                          ]),
+                          ],
                           ", self.args"
                         ])
                       ])
@@ -105,27 +89,9 @@
                             "lambda ",
                             group(["arg"]),
                             ": ",
-                            best_fitting([
-                              [
-                                [
-                                  <interned 2> [
-                                    "str(",
-                                    group([
-                                      indent([soft_line_break, group(["arg"])]),
-                                      soft_line_break
-                                    ]),
-                                    ")"
-                                  ]
-                                ]
-                              ]
-                              [[group(expand: true, [<ref interned *2>])]]
-                              [
-                                [
-                                  "(",
-                                  indent([hard_line_break, <ref interned *2>]),
-                                  hard_line_break,
-                                  ")"
-                                ]
+                            [
+                              <interned 2> [
+                                "str(",
+                                group([indent([group(["arg"])])]),
+                                ")"
                               ]
-                            ]),
-                            ", self.args"
+                            ]
                           ])
+                          <START_WITHOUT_END<Group>>
                         ])
-                      ]),
-                      ")"
+                        <START_WITHOUT_END<Indent>>
+                      ])
+                      <START_WITHOUT_END<Group>>
                     ])
+                    <START_WITHOUT_END<Group>>
                   ])
-                ]),
-                ")})\""
-              ]),
-              hard_line_break,
-              ")"
+                  <START_WITHOUT_END<Indent>>
+                ])
+                <START_WITHOUT_END<Group>>
+              ])
+              <START_WITHOUT_END<Indent>>
             ])
-          ]
+            <START_WITHOUT_END<Group>>
+          ])
+          <START_WITHOUT_END<BestFittingEntry>>
         ]
       ])
     ]),

and then the diff between main and this PR, which shows the removed brackets that represent the
nested tags:

main vs PR
@@ -38,12 +38,10 @@
                           "lambda ",
                           group(["arg"]),
                           ": ",
-                          [
-                            <interned 1> [
-                              "str(",
-                              group([indent([group(["arg"])])]),
-                              ")"
-                            ]
+                          <interned 1> [
+                            "str(",
+                            group([indent([group(["arg"])])]),
+                            ")"
                           ],
                           ", self.args"
                         ])
@@ -89,24 +87,15 @@
                             "lambda ",
                             group(["arg"]),
                             ": ",
-                            [
-                              <interned 2> [
-                                "str(",
-                                group([indent([group(["arg"])])]),
-                                ")"
-                              ]
-                            ]
+                            <interned 2> [
+                              "str(",
+                              group([indent([group(["arg"])])]),
+                              ")"
+                            ],
+                            ", self.args"
                           ])
-                          <START_WITHOUT_END<Group>>
                         ])
-                        <START_WITHOUT_END<Indent>>
-                      ])
-                      <START_WITHOUT_END<Group>>
+                      ]),
+                      ")"
                     ])
-                    <START_WITHOUT_END<Group>>
                   ])
-                  <START_WITHOUT_END<Indent>>
-                ])
-                <START_WITHOUT_END<Group>>
-              ])
-              <START_WITHOUT_END<Indent>>
+                ]),
+                ")})\""
+              ]),
+              hard_line_break,
+              ")"
             ])
-            <START_WITHOUT_END<Group>>
-          ])
-          <START_WITHOUT_END<BestFittingEntry>>
+          ]
         ]
       ])
     ]),

I think the diff between 0.15.13 and this PR is also nice to see because it makes it a bit more
clear that the BestFitting element has been inlined:

0.15.13 vs this PR
@@ -38,29 +38,11 @@
                           "lambda ",
                           group(["arg"]),
                           ": ",
-                          best_fitting([
-                            [
-                              [
-                                <interned 1> [
-                                  "str(",
-                                  group([
-                                    indent([soft_line_break, group(["arg"])]),
-                                    soft_line_break
-                                  ]),
-                                  ")"
-                                ]
-                              ]
-                            ]
-                            [[group(expand: true, [<ref interned *1>])]]
-                            [
-                              [
-                                "(",
-                                indent([hard_line_break, <ref interned *1>]),
-                                hard_line_break,
-                                ")"
-                              ]
-                            ]
-                          ]),
+                          <interned 1> [
+                            "str(",
+                            group([indent([group(["arg"])])]),
+                            ")"
+                          ],
                           ", self.args"
                         ])
                       ])
@@ -105,29 +87,11 @@
                             "lambda ",
                             group(["arg"]),
                             ": ",
-                            best_fitting([
-                              [
-                                [
-                                  <interned 2> [
-                                    "str(",
-                                    group([
-                                      indent([soft_line_break, group(["arg"])]),
-                                      soft_line_break
-                                    ]),
-                                    ")"
-                                  ]
-                                ]
-                              ]
-                              [[group(expand: true, [<ref interned *2>])]]
-                              [
-                                [
-                                  "(",
-                                  indent([hard_line_break, <ref interned *2>]),
-                                  hard_line_break,
-                                  ")"
-                                ]
-                              ]
-                            ]),
+                            <interned 2> [
+                              "str(",
+                              group([indent([group(["arg"])])]),
+                              ")"
+                            ],
                             ", self.args"
                           ])
                         ])
@@ -155,7 +119,6 @@

     def __str__(self) -> str:
         return (
-            f"first stringlist of args long={list(map(lambda arg: str(
-                        arg
-                    ), self.args))})"
+            "first string"
+            f"list of args long={list(map(lambda arg: str(arg), self.args))})"
         )

And you can also see at the bottom here that the actual formatting that #25144 sought to resolve
is still fixed.

Test Plan

New regression test based on the code in the issue

ntBre added 3 commits May 26, 2026 13:50
Summary
--

This PR fixes #25397 by dropping `StartBestFittingEntry` and `EndBestFittingEntry` tags in
`RemoveSoftLinesBuffer`. #25144 changed `RemoveSoftLinesBuffer` to inline the most-flat variant of
`BestFitting` elements, but it inlined them with their start and end tags, which caused the problem
observed in #25397 when these tags became nested in another `BestFitting` element, which interpreted
the first `End` tag as ending the outer `BestFitting` rather than the nested one. You can see this
clearly in the IR. First, the diff between the 0.15.13 and 0.15.14 IR, which reveals the unmatched
start tags:

<details>
<summary>0.15.13 vs main</summary>

```diff
@@ -38,29 +38,13 @@
                           "lambda ",
                           group(["arg"]),
                           ": ",
-                          best_fitting([
-                            [
-                              [
-                                <interned 1> [
-                                  "str(",
-                                  group([
-                                    indent([soft_line_break, group(["arg"])]),
-                                    soft_line_break
-                                  ]),
-                                  ")"
-                                ]
-                              ]
-                            ]
-                            [[group(expand: true, [<ref interned *1>])]]
-                            [
-                              [
-                                "(",
-                                indent([hard_line_break, <ref interned *1>]),
-                                hard_line_break,
-                                ")"
-                              ]
+                          [
+                            <interned 1> [
+                              "str(",
+                              group([indent([group(["arg"])])]),
+                              ")"
                             ]
-                          ]),
+                          ],
                           ", self.args"
                         ])
                       ])
@@ -105,27 +89,9 @@
                             "lambda ",
                             group(["arg"]),
                             ": ",
-                            best_fitting([
-                              [
-                                [
-                                  <interned 2> [
-                                    "str(",
-                                    group([
-                                      indent([soft_line_break, group(["arg"])]),
-                                      soft_line_break
-                                    ]),
-                                    ")"
-                                  ]
-                                ]
-                              ]
-                              [[group(expand: true, [<ref interned *2>])]]
-                              [
-                                [
-                                  "(",
-                                  indent([hard_line_break, <ref interned *2>]),
-                                  hard_line_break,
-                                  ")"
-                                ]
+                            [
+                              <interned 2> [
+                                "str(",
+                                group([indent([group(["arg"])])]),
+                                ")"
                               ]
-                            ]),
-                            ", self.args"
+                            ]
                           ])
+                          <START_WITHOUT_END<Group>>
                         ])
-                      ]),
-                      ")"
+                        <START_WITHOUT_END<Indent>>
+                      ])
+                      <START_WITHOUT_END<Group>>
                     ])
+                    <START_WITHOUT_END<Group>>
                   ])
-                ]),
-                ")})\""
-              ]),
-              hard_line_break,
-              ")"
+                  <START_WITHOUT_END<Indent>>
+                ])
+                <START_WITHOUT_END<Group>>
+              ])
+              <START_WITHOUT_END<Indent>>
             ])
-          ]
+            <START_WITHOUT_END<Group>>
+          ])
+          <START_WITHOUT_END<BestFittingEntry>>
         ]
       ])
     ]),
```

</details>

and then the diff between main and this PR, which shows the removed brackets that represent the
nested tags:

<details>
<summary>main vs PR</summary>

```diff
@@ -38,12 +38,10 @@
                           "lambda ",
                           group(["arg"]),
                           ": ",
-                          [
-                            <interned 1> [
-                              "str(",
-                              group([indent([group(["arg"])])]),
-                              ")"
-                            ]
+                          <interned 1> [
+                            "str(",
+                            group([indent([group(["arg"])])]),
+                            ")"
                           ],
                           ", self.args"
                         ])
@@ -89,24 +87,15 @@
                             "lambda ",
                             group(["arg"]),
                             ": ",
-                            [
-                              <interned 2> [
-                                "str(",
-                                group([indent([group(["arg"])])]),
-                                ")"
-                              ]
-                            ]
+                            <interned 2> [
+                              "str(",
+                              group([indent([group(["arg"])])]),
+                              ")"
+                            ],
+                            ", self.args"
                           ])
-                          <START_WITHOUT_END<Group>>
                         ])
-                        <START_WITHOUT_END<Indent>>
-                      ])
-                      <START_WITHOUT_END<Group>>
+                      ]),
+                      ")"
                     ])
-                    <START_WITHOUT_END<Group>>
                   ])
-                  <START_WITHOUT_END<Indent>>
-                ])
-                <START_WITHOUT_END<Group>>
-              ])
-              <START_WITHOUT_END<Indent>>
+                ]),
+                ")})\""
+              ]),
+              hard_line_break,
+              ")"
             ])
-            <START_WITHOUT_END<Group>>
-          ])
-          <START_WITHOUT_END<BestFittingEntry>>
+          ]
         ]
       ])
     ]),
```

</details>

I think the diff between 0.15.13 and this PR is also nice to see because it makes it a bit more
clear that the `BestFitting` element has been inlined:

<details>
<summary>0.15.13 vs this PR</summary>

```diff
@@ -38,29 +38,11 @@
                           "lambda ",
                           group(["arg"]),
                           ": ",
-                          best_fitting([
-                            [
-                              [
-                                <interned 1> [
-                                  "str(",
-                                  group([
-                                    indent([soft_line_break, group(["arg"])]),
-                                    soft_line_break
-                                  ]),
-                                  ")"
-                                ]
-                              ]
-                            ]
-                            [[group(expand: true, [<ref interned *1>])]]
-                            [
-                              [
-                                "(",
-                                indent([hard_line_break, <ref interned *1>]),
-                                hard_line_break,
-                                ")"
-                              ]
-                            ]
-                          ]),
+                          <interned 1> [
+                            "str(",
+                            group([indent([group(["arg"])])]),
+                            ")"
+                          ],
                           ", self.args"
                         ])
                       ])
@@ -105,29 +87,11 @@
                             "lambda ",
                             group(["arg"]),
                             ": ",
-                            best_fitting([
-                              [
-                                [
-                                  <interned 2> [
-                                    "str(",
-                                    group([
-                                      indent([soft_line_break, group(["arg"])]),
-                                      soft_line_break
-                                    ]),
-                                    ")"
-                                  ]
-                                ]
-                              ]
-                              [[group(expand: true, [<ref interned *2>])]]
-                              [
-                                [
-                                  "(",
-                                  indent([hard_line_break, <ref interned *2>]),
-                                  hard_line_break,
-                                  ")"
-                                ]
-                              ]
-                            ]),
+                            <interned 2> [
+                              "str(",
+                              group([indent([group(["arg"])])]),
+                              ")"
+                            ],
                             ", self.args"
                           ])
                         ])
@@ -155,7 +119,6 @@

     def __str__(self) -> str:
         return (
-            f"first stringlist of args long={list(map(lambda arg: str(
-                        arg
-                    ), self.args))})"
+            "first string"
+            f"list of args long={list(map(lambda arg: str(arg), self.args))})"
         )
```

</details>

And you can also see at the bottom here that the actual formatting that #25144 sought to resolve
is still fixed.

Test Plan
--

New regression test based on the code in the issue
@ntBre ntBre added bug Something isn't working formatter Related to the formatter labels May 26, 2026
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot Bot commented May 26, 2026

ruff-ecosystem results

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

@ntBre ntBre marked this pull request as ready for review May 26, 2026 18:20
@ntBre ntBre requested a review from MichaReiser as a code owner May 26, 2026 18:20
@ntBre ntBre merged commit 9c6536e into main May 26, 2026
73 of 74 checks passed
@ntBre ntBre deleted the brent/lambda-group branch May 26, 2026 20:54
anishgirianish pushed a commit to anishgirianish/ruff that referenced this pull request May 28, 2026
…sh#25398)

Summary
--

This PR fixes astral-sh#25397 by dropping `StartBestFittingEntry` and
`EndBestFittingEntry` tags in
`RemoveSoftLinesBuffer`. astral-sh#25144 changed `RemoveSoftLinesBuffer` to
inline the most-flat variant of
`BestFitting` elements, but it inlined them with their start and end
tags, which caused the problem
observed in astral-sh#25397 when these tags became nested in another
`BestFitting` element, which interpreted
the first `End` tag as ending the outer `BestFitting` rather than the
nested one. You can see this
clearly in the IR. First, the diff between the 0.15.13 and 0.15.14 IR,
which reveals the unmatched
start tags:

<details>
<summary>0.15.13 vs main</summary>

```diff
@@ -38,29 +38,13 @@
                           "lambda ",
                           group(["arg"]),
                           ": ",
-                          best_fitting([
-                            [
-                              [
-                                <interned 1> [
-                                  "str(",
-                                  group([
-                                    indent([soft_line_break, group(["arg"])]),
-                                    soft_line_break
-                                  ]),
-                                  ")"
-                                ]
-                              ]
-                            ]
-                            [[group(expand: true, [<ref interned *1>])]]
-                            [
-                              [
-                                "(",
-                                indent([hard_line_break, <ref interned *1>]),
-                                hard_line_break,
-                                ")"
-                              ]
+                          [
+                            <interned 1> [
+                              "str(",
+                              group([indent([group(["arg"])])]),
+                              ")"
                             ]
-                          ]),
+                          ],
                           ", self.args"
                         ])
                       ])
@@ -105,27 +89,9 @@
                             "lambda ",
                             group(["arg"]),
                             ": ",
-                            best_fitting([
-                              [
-                                [
-                                  <interned 2> [
-                                    "str(",
-                                    group([
-                                      indent([soft_line_break, group(["arg"])]),
-                                      soft_line_break
-                                    ]),
-                                    ")"
-                                  ]
-                                ]
-                              ]
-                              [[group(expand: true, [<ref interned *2>])]]
-                              [
-                                [
-                                  "(",
-                                  indent([hard_line_break, <ref interned *2>]),
-                                  hard_line_break,
-                                  ")"
-                                ]
+                            [
+                              <interned 2> [
+                                "str(",
+                                group([indent([group(["arg"])])]),
+                                ")"
                               ]
-                            ]),
-                            ", self.args"
+                            ]
                           ])
+                          <START_WITHOUT_END<Group>>
                         ])
-                      ]),
-                      ")"
+                        <START_WITHOUT_END<Indent>>
+                      ])
+                      <START_WITHOUT_END<Group>>
                     ])
+                    <START_WITHOUT_END<Group>>
                   ])
-                ]),
-                ")})\""
-              ]),
-              hard_line_break,
-              ")"
+                  <START_WITHOUT_END<Indent>>
+                ])
+                <START_WITHOUT_END<Group>>
+              ])
+              <START_WITHOUT_END<Indent>>
             ])
-          ]
+            <START_WITHOUT_END<Group>>
+          ])
+          <START_WITHOUT_END<BestFittingEntry>>
         ]
       ])
     ]),
```

</details>

and then the diff between main and this PR, which shows the removed
brackets that represent the
nested tags:

<details>
<summary>main vs PR</summary>

```diff
@@ -38,12 +38,10 @@
                           "lambda ",
                           group(["arg"]),
                           ": ",
-                          [
-                            <interned 1> [
-                              "str(",
-                              group([indent([group(["arg"])])]),
-                              ")"
-                            ]
+                          <interned 1> [
+                            "str(",
+                            group([indent([group(["arg"])])]),
+                            ")"
                           ],
                           ", self.args"
                         ])
@@ -89,24 +87,15 @@
                             "lambda ",
                             group(["arg"]),
                             ": ",
-                            [
-                              <interned 2> [
-                                "str(",
-                                group([indent([group(["arg"])])]),
-                                ")"
-                              ]
-                            ]
+                            <interned 2> [
+                              "str(",
+                              group([indent([group(["arg"])])]),
+                              ")"
+                            ],
+                            ", self.args"
                           ])
-                          <START_WITHOUT_END<Group>>
                         ])
-                        <START_WITHOUT_END<Indent>>
-                      ])
-                      <START_WITHOUT_END<Group>>
+                      ]),
+                      ")"
                     ])
-                    <START_WITHOUT_END<Group>>
                   ])
-                  <START_WITHOUT_END<Indent>>
-                ])
-                <START_WITHOUT_END<Group>>
-              ])
-              <START_WITHOUT_END<Indent>>
+                ]),
+                ")})\""
+              ]),
+              hard_line_break,
+              ")"
             ])
-            <START_WITHOUT_END<Group>>
-          ])
-          <START_WITHOUT_END<BestFittingEntry>>
+          ]
         ]
       ])
     ]),
```

</details>

I think the diff between 0.15.13 and this PR is also nice to see because
it makes it a bit more
clear that the `BestFitting` element has been inlined:

<details>
<summary>0.15.13 vs this PR</summary>

```diff
@@ -38,29 +38,11 @@
                           "lambda ",
                           group(["arg"]),
                           ": ",
-                          best_fitting([
-                            [
-                              [
-                                <interned 1> [
-                                  "str(",
-                                  group([
-                                    indent([soft_line_break, group(["arg"])]),
-                                    soft_line_break
-                                  ]),
-                                  ")"
-                                ]
-                              ]
-                            ]
-                            [[group(expand: true, [<ref interned *1>])]]
-                            [
-                              [
-                                "(",
-                                indent([hard_line_break, <ref interned *1>]),
-                                hard_line_break,
-                                ")"
-                              ]
-                            ]
-                          ]),
+                          <interned 1> [
+                            "str(",
+                            group([indent([group(["arg"])])]),
+                            ")"
+                          ],
                           ", self.args"
                         ])
                       ])
@@ -105,29 +87,11 @@
                             "lambda ",
                             group(["arg"]),
                             ": ",
-                            best_fitting([
-                              [
-                                [
-                                  <interned 2> [
-                                    "str(",
-                                    group([
-                                      indent([soft_line_break, group(["arg"])]),
-                                      soft_line_break
-                                    ]),
-                                    ")"
-                                  ]
-                                ]
-                              ]
-                              [[group(expand: true, [<ref interned *2>])]]
-                              [
-                                [
-                                  "(",
-                                  indent([hard_line_break, <ref interned *2>]),
-                                  hard_line_break,
-                                  ")"
-                                ]
-                              ]
-                            ]),
+                            <interned 2> [
+                              "str(",
+                              group([indent([group(["arg"])])]),
+                              ")"
+                            ],
                             ", self.args"
                           ])
                         ])
@@ -155,7 +119,6 @@

     def __str__(self) -> str:
         return (
-            f"first stringlist of args long={list(map(lambda arg: str(
-                        arg
-                    ), self.args))})"
+            "first string"
+            f"list of args long={list(map(lambda arg: str(arg), self.args))})"
         )
```

</details>

And you can also see at the bottom here that the actual formatting that
astral-sh#25144 sought to resolve
is still fixed.

Test Plan
--

New regression test based on the code in the issue
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.

Can't format valid python file: "Invalid document: Expected end tag of kind Group but found Indent."

2 participants