Commit Graph

876 Commits

Author SHA1 Message Date
Brent Westbrook fe255d1ad2
leading_comments in block 2025-12-05 13:39:37 -05:00
Brent Westbrook c7666423ba
block indent and trailing comments 2025-12-05 13:22:09 -05:00
Brent Westbrook 25d70b408b
more tests 2025-12-05 12:50:18 -05:00
Brent Westbrook 5605387a77
add another dangling case between lambda and parameters 2025-12-05 10:22:32 -05:00
Brent Westbrook dc240a1574
clippy 2025-12-05 09:32:46 -05:00
Brent Westbrook 43b53edcab
improve dangling header comment placement 2025-12-05 09:21:48 -05:00
Brent Westbrook a4b4a82e61
add another dangling eol case 2025-12-05 08:34:55 -05:00
Brent Westbrook 219bbd1ce0
check comment case first 2025-12-05 08:26:58 -05:00
Brent Westbrook dfd3460c7a
add some more tests 2025-12-04 18:02:52 -05:00
Brent Westbrook 08b1da3ab0
mirror comment handling from `maybe_parenthesize_expression`
and update comment
2025-12-04 15:44:36 -05:00
Brent Westbrook 1a3e385a8e
Merge branch 'main' into brent/indent-lambda-params 2025-12-04 10:39:43 -05:00
Brent Westbrook bdd5ba5e7f
gate optional_parentheses branches behind stable 2025-12-04 10:37:33 -05:00
Brent Westbrook 3a20c6f196
copy mapper test case from can_omit_optional_parentheses 2025-12-04 10:25:13 -05:00
Brent Westbrook 2e84402f69
add comments and some supporting tests 2025-12-04 10:08:46 -05:00
Brent Westbrook afb01ce84d
combine preview checks 2025-12-04 09:50:39 -05:00
Brent Westbrook 7dddcc85bc
remove comment
I don't think we can move the `fits_expanded` call into the assignment
formatting because that would wrap the whole lambda in a `fits_expanded`, when we
just want to wrap the lambda body in it instead. if I understand correctly, we'd
need to duplicate basically this whole function to inject `fits_expanded` in the
right place for the lambda formatting in assignments
2025-12-03 14:27:42 -05:00
Brent Westbrook 04963a6b6b
expand parent if the lambda body breaks 2025-12-03 14:16:16 -05:00
Brent Westbrook 258b1fd7eb
add wrapping case from the ecosystem check
the lambda is hugging its enclosing parentheses when it shouldn't be. there
seems to be an issue with `best_fitting!` because moving any of the options
we're passing to it out of `best_fitting!` avoids this behavior.

IR:

```
[
  source_position(0),
  source_position(1),
  "[",
  group(expand: propagated, [
    indent([
      soft_line_break,
      "(",
      group([
        indent([
          soft_line_break,
          "lambda ",
          group(["eval_df, _"]),
          ": ",
          best_fitting([
            [
              [
                <interned 0> [
                  "MetricValue(",
                  group(expand: propagated, [
                    indent([
                      soft_line_break,
                      group(expand: propagated, [
                        "scores=eval_df[",
                        group([
                          indent([soft_line_break, "\"prediction\""]),
                          soft_line_break
                        ]),
                        "].tolist",
                        group(["()"]),
                        ",",
                        soft_line_break_or_space,
                        "aggregate_results={",
                        group([
                          indent([
                            soft_line_break,
                            group([
                              "\"prediction_sum\": sum(",
                              group([
                                indent([
                                  soft_line_break,
                                  group([
                                    "eval_df[",
                                    group([
                                      indent([soft_line_break, "\"prediction\""]),
                                      soft_line_break
                                    ]),
                                    "]"
                                  ])
                                ]),
                                soft_line_break
                              ]),
                              ")"
                            ])
                          ]),
                          soft_line_break
                        ]),
                        "}",
                        if_group_breaks([","]),
                        expand_parent
                      ])
                    ]),
                    soft_line_break
                  ]),
                  ")"
                ]
              ]
            ]
            [[group(expand: true, [<ref interned *0>])]]
            [
              [
                "(",
                indent([hard_line_break, <ref interned *0>]),
                hard_line_break,
                ")"
              ]
            ]
          ])
        ]),
        soft_line_break
      ]),
      ")",
      if_group_breaks([","]),
      expand_parent
    ]),
    soft_line_break
  ]),
  "]",
  source_position(196),
  hard_line_break,
  source_position(196)
]
```
2025-12-03 11:52:48 -05:00
Brent Westbrook a3400a017a
use parenthesize_if_expands for fluent call chains 2025-12-03 11:51:02 -05:00
Brent Westbrook 9ef9d0302d
fix another ecosystem call expansion 2025-12-03 10:06:48 -05:00
Brent Westbrook 6f6c09c72a
fix snapshot changes for cases with comments 2025-12-03 09:45:00 -05:00
Brent Westbrook efa372b379
apply Micha's patch, fixing everything?
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-03 09:14:52 -05:00
Brent Westbrook 97850661fd
add too-eagerly parenthesized case from ecosystem
the initial code here is only 83 characters wide, so the call expression should
fit without wrapping the whole body in parens
2025-12-02 15:34:32 -05:00
Brent Westbrook 2e9500209f
avoid nesting groups 2025-12-02 15:30:12 -05:00
Brent Westbrook 972129c0ef
create id only in indented case, update group name 2025-12-02 15:24:31 -05:00
Brent Westbrook 18a3d59352
use write!
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-02 15:20:57 -05:00
Micha Reiser 515de2d062
Move `Token`, `TokenKind` and `Tokens` to `ruff-python-ast` (#21760) 2025-12-02 20:10:46 +01:00
Brent Westbrook 89dc1ada39
add a couple more test cases
I tried to get Claude to come up with tests, but most of them weren't very
interesting. I think these two additional types of assignments might be worth
having, though.
2025-12-02 11:47:41 -05:00
Brent Westbrook e9f9507dc8
add some assignment tests with parentheses and comments 2025-12-02 11:47:04 -05:00
Brent Westbrook 0a41abed52
avoid lambda special-casing in maybe_parenthesize_expression 2025-12-02 11:28:27 -05:00
Brent Westbrook 24e15bfd95
exclude call and subscript expressions from has_own_parentheses 2025-12-02 11:28:19 -05:00
Brent Westbrook 9db5d43e18
possibly bad test for triple-quoted f-strings
parenthesizing these seems redundant. I would prefer our old formatting more
like this:

```py
def ddb():
    sql = (
        lambda var, table, n=N: f"""
        CREATE TABLE {table} AS
        SELECT ROW_NUMBER() OVER () AS id, {var}
        FROM (
            SELECT {var}
            FROM RANGE({n}) _ ({var})
            ORDER BY RANDOM()
        )
        """
    )
```

where the `f"""` serves as the parentheses, instead of the current:

```py
def ddb():
    sql = lambda var, table, n=N: (
        f"""
        CREATE TABLE {table} AS
        SELECT ROW_NUMBER() OVER () AS id, {var}
        FROM (
            SELECT {var}
            FROM RANGE({n}) _ ({var})
            ORDER BY RANDOM()
        )
        """
    )
```
2025-12-02 11:22:55 -05:00
Brent Westbrook ad3147703d
another bad test for long bodies with their own parens
this case ends up too long at 108 columns:

```py
class C:
    def foo():
        if True:
            transaction_count = self._query_txs_for_range(
                get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range(
                    chain_id=_chain_id,
                    from_ts=from_ts,
                    to_ts=to_ts,
                ),
            )
```

instead, it should be formatted like this, fitting within 88 columns:

```py
class C:
    def foo():
        if True:
            transaction_count = self._query_txs_for_range(
                get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: (
				    db_evmtx.count_transactions_in_range(
                        chain_id=_chain_id,
                        from_ts=from_ts,
                        to_ts=to_ts,
					)
                ),
            )
```

we can fix this by removing the `has_own_parentheses` check in the new lambda
formatting, but this breaks other cases. we might want to preserve this? in this
specific ecosystem case, the project has a `noqa: E501` comment, so this seems
to be what they want anyway, although we don't know that when formatting
2025-12-02 11:22:55 -05:00
Brent Westbrook f634bb5247
propagate lambda layout for annotated assignments 2025-12-02 11:22:55 -05:00
Brent Westbrook 6b47664019
add another bad test case from the ecosystem report
I would expect this to format as:

```py
class C:
    _is_recognized_dtype: Callable[[DtypeObj], bool] = lambda x: (
        lib.is_np_dtype(x, "M") or isinstance(x, DatetimeTZDtype)
    )
```

instead of the current:

```py
class C:
    _is_recognized_dtype: Callable[[DtypeObj], bool] = (
        lambda x: lib.is_np_dtype(x, "M") or isinstance(x, DatetimeTZDtype)
    )
```
2025-12-02 11:22:55 -05:00
Brent Westbrook 07bcf41a34
fix binary expression in lambda in return 2025-12-02 11:22:55 -05:00
Brent Westbrook d68a03a519
add another bad case from the ecosystem check
this should format like:

```py
def foo():
    if True:
        if True:
            return lambda x: (
                np.exp(cs(np.log(x.to(u.MeV).value))) * u.MeV * u.cm**2 / u.g
            )
```

instead of the current snapshot:

```diff
 def foo():
     if True:
         if True:
-            return (
-                lambda x: np.exp(cs(np.log(x.to(u.MeV).value))) * u.MeV * u.cm**2 / u.g
-            )
+            return lambda x: np.exp(
+                cs(np.log(x.to(u.MeV).value))
+            ) * u.MeV * u.cm**2 / u.g
```
2025-12-02 11:22:55 -05:00
Brent Westbrook 62c968c826
rough draft of ExprLambdaLayout::Assignment 2025-12-02 11:22:55 -05:00
Brent Westbrook a5e13cf140
add an unstable test case from ecosystem report
```diff
-name = re.sub(r"[^\x21\x23-\x5b\x5d-\x7e]...............", lambda m: (
-        f"\\{m.group(0)}"
-    ), p["name"])
+name = re.sub(
+    r"[^\x21\x23-\x5b\x5d-\x7e]...............",
+    lambda m: (f"\\{m.group(0)}"),
+    p["name"],
+)
```

the second format is actually fine, but the first one obviously looks horrible,
even if it were stable
2025-12-02 11:22:55 -05:00
Brent Westbrook 74093bdb50
add a poorly formatted case from the ecosystem report
this formats as:

```py
class C:
    function_dict: Dict[Text, Callable[[CRFToken], Any]] = {
        CRFEntityExtractorOptions.POS2: lambda crf_token: crf_token.pos_tag[
            :2
        ] if crf_token.pos_tag is not None else None,
    }
```

when I think it should look like:

```py
class C:
    function_dict: Dict[Text, Callable[[CRFToken], Any]] = {
        CRFEntityExtractorOptions.POS2: lambda crf_token: (
		    crf_token.pos_tag[:2] if crf_token.pos_tag is not None else None,
		)
    }
```
2025-12-02 11:22:55 -05:00
Brent Westbrook 1b58643040
wip: parenthesize long lambda bodies 2025-12-02 11:22:55 -05:00
Brent Westbrook 8cc884428d
keep lambda parameters on a single line 2025-12-02 11:22:55 -05:00
Brent Westbrook 1e70c991a2
baseline test cases 2025-12-02 11:22:55 -05:00
William Woodruff edc6ed5077
Use `npm ci --ignore-scripts` everywhere (#21742) 2025-12-01 17:13:52 -05:00
Dylan 62343a101a
Respect `fmt: skip` for compound statements on single line (#20633)
Closes #11216

Essentially the approach is to implement `Format` for a new struct
`FormatClause` which is just a clause header _and_ its body. We then
have the information we need to see whether there is a skip suppression
comment on the last child in the body and it all fits on one line.
2025-11-18 12:02:09 -06:00
Brent Westbrook cbc6863b8c
Fix panic when formatting comments in unary expressions (#21501)
## Summary

This is another attempt at https://github.com/astral-sh/ruff/pull/21410
that fixes https://github.com/astral-sh/ruff/issues/19226.

@MichaReiser helped me get something working in a very helpful pairing
session. I pushed one additional commit moving the comments back from
leading comments to trailing comments, which I think retains more of the
input formatting.

I was inspired by Dylan's PR (#21185) to make one of these tables:

<table>
                <thead>
                    <tr>
                    <th scope="col">Input</th>
                    <th scope="col">Main</th>
                    <th scope="col">PR</th>
                    </tr>
                </thead>
                <tbody>
<tr>
<td><pre lang="python">
if (
    not
    # comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
    pass
</pre></td>
<td><pre lang="python">
if (
    # comment
    not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
    pass

</pre></td>
<td><pre lang="python">
if (
    not
    # comment
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
    pass

</pre></td>
</tr>
<tr>
<td><pre lang="python">
if (
    # unary comment
    not
    # operand comment
    (
        # comment
        aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
        + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
    )
):
    pass
</pre></td>
<td><pre lang="python">
if (
    # unary comment
    # operand comment
    not (
        # comment
        aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
        + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
    )
):
    pass

</pre></td>
<td><pre lang="python">
if (
    # unary comment
    not
    # operand comment
    (
        # comment
        aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
        + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
    )
):
    pass

</pre></td>
</tr>
<tr>
<td><pre lang="python">
if (
    not # comment
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
    pass
</pre></td>
<td><pre lang="python">
if (  # comment
    not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
    pass

</pre></td>
<td><pre lang="python">
if (
    not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa  # comment
    + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
    pass

</pre></td>
</tr>
</tbody>
            </table>

hopefully it helps even though the snippets are much wider here.

The two main differences are (1) that we now retain own-line comments
between the unary operator and its operand instead of moving these to
leading comments on the operator itself, and (2) that we move
end-of-line comments between the operator and operand to dangling
end-of-line comments on the operand (the last example in the table).

## Test Plan

Existing tests, plus new ones based on the issue. As I noted below, I
also ran the output from main on the unary.py file back through this
branch to check that we don't reformat code from main. This made me feel
a bit better about not preview-gating the changes in this PR.

```shell
> git show main:crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/unary.py | ruff format - | ./target/debug/ruff format --diff -
> echo $?
0
```

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
Co-authored-by: Takayuki Maeda <takoyaki0316@gmail.com>
2025-11-18 10:48:14 -05:00
Dylan 8156b45173
Avoid syntax error when formatting attribute expressions with outer parentheses, parenthesized value, and trailing comment on value (#20418)
Closes #19350 

This fixes a syntax error caused by formatting. However, the new tests reveal that there are some cases where formatting attributes with certain comments behaves strangely, both before and after this PR, so some more polish may be in order.

For example, without parentheses around the value, and both before and after this PR, we have:

```python
# unformatted
variable = (
    something # a comment
    .first_method("some string")
)

# formatted
variable = something.first_method("some string")  # a comment
```

which is probably not where the comment ought to go.
2025-11-17 09:11:36 -06:00
Dylan 04a3ec3689
Adjust own-line comment placement between branches (#21185)
This PR attempts to improve the placement of own-line comments between
branches in the setting where the comment is more indented than the
preceding node.

There are two main changes.

### First change: Preceding node has leading content

If the preceding node has leading content, we now regard the comment as
automatically _less_ indented than the preceding node, and format
accordingly.

For example, 

```python
if True: preceding_node
# leading on `else`, not trailing on `preceding_node`
else: ...
```

This is more compatible with `black`, although there is a (presumably
very uncommon) edge case:

```python
if True:
    this;that
    # leading on `else`, but trailing in `black`
else: ...
```

I'm sort of okay with this - presumably if one wanted a comment for
those semi-colon separated statements, one should have put it _above_
them, and one wanted a comment only for `that` then it ought to have
been on the same line?

### Second change: searching for last child in body

While searching for the (recursively) last child in the body of the
preceding _branch_, we implicitly assumed that the preceding node had to
have a body to begin the recursion. But actually, in the base case, the
preceding node _is_ the last child in the body of the preceding branch.
So, for example:

```python
if True:
    something
    last_child_but_no_body
    # leading on else for `main` but trailing in this PR
else: ...
```

### More examples

The table below is an attempt to summarize the changes in behavior. The
rows alternate between an example snippet with `while` and the same
example with `if` - in the former case we do _not_ have an `else` node
and in the latter we do.

Notice that:

1. On `main` our handling of `if` vs. `while` is not consistent, whereas
it is consistent in the present PR
2. We disagree with `black` in all cases except that last example on
`main`, but agree in all cases for the present PR (though see above for
a wonky edge case where we disagree).

<table>
<tr>
<th>Original
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>

<th><code>main</code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>
<th>This
PR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>

<th><code>black</code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>
</tr>
<tr>
<td>

<pre lang="python">
while True: 
    pass
        # comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
while True:
    pass
else:
    # comment
    pass
</pre>

</td>
<td>

<pre lang="python">
while True:
    pass
    # comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
while True:
    pass
    # comment
else:
    pass
</pre>

</td>
</tr>
<tr>
<td>

<pre lang="python">
if True:
    pass
        # comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
if True:
    pass
# comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
if True:
    pass
    # comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
if True:
    pass
    # comment
else:
    pass
</pre>

</td>
</tr>
<tr>
<td>

<pre lang="python">
while True: pass
# comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
while True:
    pass
    # comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
while True:
    pass
# comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
while True:
    pass
# comment
else:
    pass
</pre>

</td>
</tr>
<tr>
<td>

<pre lang="python">
if True: pass
# comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
if True:
    pass
    # comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
if True:
    pass
# comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
if True:
    pass
# comment
else:
    pass
</pre>

</td>
</tr>
<tr>
<td>

<pre lang="python">
while True: pass
    # comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
while True:
    pass
else:
    # comment
    pass
</pre>

</td>
<td>

<pre lang="python">
while True:
    pass
# comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
while True:
    pass
# comment
else:
    pass
</pre>

</td>
</tr>
<tr>
<td>

<pre lang="python">
if True: pass
    # comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
if True:
    pass
# comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
if True:
    pass
# comment
else:
    pass
</pre>

</td>
<td>

<pre lang="python">
if True:
    pass
# comment
else:
    pass
</pre>

</td>
</tr>
</table>
2025-11-17 07:30:34 -06:00
Brent Westbrook 63b1c1ea8b
Avoid extra parentheses for long `match` patterns with `as` captures (#21176)
Summary
--

This PR fixes #17796 by taking the approach mentioned in
https://github.com/astral-sh/ruff/issues/17796#issuecomment-2847943862
of simply recursing into the `MatchAs` patterns when checking if we need
parentheses. This allows us to reuse the parentheses in the inner
pattern before also breaking the `MatchAs` pattern itself:

```diff
 match class_pattern:
     case Class(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) as capture:
         pass
-    case (
-        Class(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) as capture
-    ):
+    case Class(
+        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+    ) as capture:
         pass
-    case (
-        Class(
-            xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-        ) as capture
-    ):
+    case Class(
+        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+    ) as capture:
         pass
     case (
         Class(
@@ -685,13 +683,11 @@
 match sequence_pattern_brackets:
     case [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] as capture:
         pass
-    case (
-        [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] as capture
-    ):
+    case [
+        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+    ] as capture:
         pass
-    case (
-        [
-            xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-        ] as capture
-    ):
+    case [
+        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+    ] as capture:
         pass
```

I haven't really resolved the question of whether or not it's okay
always to recurse, but I'm hoping the ecosystem check on this PR might
shed some light on that.

Test Plan
--

New tests based on the issue and then reviewing the ecosystem check here
2025-11-03 17:06:52 -05:00
Alex Waygood 39f105bc4a
[ty] Use "cannot" consistently over "can not" (#21255) 2025-11-03 10:38:20 -05:00