ruff/crates/ruff_python_formatter/src/expression
Brent Westbrook 0ebdebddd8
Keep lambda parameters on one line and parenthesize the body if it expands (#21385)
## Summary

This PR makes two changes to our formatting of `lambda` expressions:
1. We now parenthesize the body expression if it expands
2. We now try to keep the parameters on a single line

The latter of these fixes #8179:

Black formatting and this PR's formatting:

```py
def a():
    return b(
        c,
        d,
        e,
        f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
            *args, **kwargs
        ),
    )
```

Stable Ruff formatting

```py
def a():
    return b(
        c,
        d,
        e,
        f=lambda self,
        *args,
        **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs),
    )
```

We don't parenthesize the body expression here because the call to
`aaaa...` has its own parentheses, but adding a binary operator shows
the new parenthesization:

```diff
@@ -3,7 +3,7 @@
         c,
         d,
         e,
-        f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
-            *args, **kwargs
-        ) + 1,
+        f=lambda self, *args, **kwargs: (
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1
+        ),
     )
```

This is actually a new divergence from Black, which formats this input
like this:

```py
def a():
    return b(
        c,
        d,
        e,
        f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
            *args, **kwargs
        )
        + 1,
    )
```

But I think this is an improvement, unlike the case from #8179.

One other, smaller benefit is that because we now add parentheses to
lambda bodies, we also remove redundant parentheses:

```diff
 @pytest.mark.parametrize(
     "f",
     [
-        lambda x: (x.expanding(min_periods=5).cov(x, pairwise=True)),
-        lambda x: (x.expanding(min_periods=5).corr(x, pairwise=True)),
+        lambda x: x.expanding(min_periods=5).cov(x, pairwise=True),
+        lambda x: x.expanding(min_periods=5).corr(x, pairwise=True),
     ],
 )
 def test_moment_functions_zero_length_pairwise(f):
```

## Test Plan

New tests taken from #8465 and probably a few more I should grab from
the ecosystem results.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-12-12 12:02:25 -05:00
..
binary_like.rs Enable `--document-private-items` for `ruff_python_formatter` (#21903) 2025-12-11 08:23:10 -05:00
expr_attribute.rs Avoid syntax error when formatting attribute expressions with outer parentheses, parenthesized value, and trailing comment on value (#20418) 2025-11-17 09:11:36 -06:00
expr_await.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_bin_op.rs Fix f-string formatting in assignment statement (#14454) 2024-11-26 15:07:18 +05:30
expr_bool_op.rs Move {AnyNodeRef, AstNode} to ruff_python_ast crate root (#8030) 2023-10-18 00:01:18 +00:00
expr_boolean_literal.rs Split `Constant` to individual literal nodes (#8064) 2023-10-30 12:13:23 +05:30
expr_bytes_literal.rs Switch to Rust 2024 edition (#18129) 2025-05-16 13:25:28 +02:00
expr_call.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_compare.rs Fix f-string formatting in assignment statement (#14454) 2024-11-26 15:07:18 +05:30
expr_dict.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_dict_comp.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_ellipsis_literal.rs Split `Constant` to individual literal nodes (#8064) 2023-10-30 12:13:23 +05:30
expr_f_string.rs Implement template strings (#17851) 2025-05-30 15:00:56 -05:00
expr_generator.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_if.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_ipy_escape_command.rs Formatter parentheses support for `IpyEscapeCommand` (#8207) 2023-10-25 14:01:50 +00:00
expr_lambda.rs Keep lambda parameters on one line and parenthesize the body if it expands (#21385) 2025-12-12 12:02:25 -05:00
expr_list.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_list_comp.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_name.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_named.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_none_literal.rs Split `Constant` to individual literal nodes (#8064) 2023-10-30 12:13:23 +05:30
expr_number_literal.rs Update Rust toolchain to 1.89 (#19807) 2025-08-07 18:21:50 +02:00
expr_set.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_set_comp.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_slice.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_starred.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_string_literal.rs Switch to Rust 2024 edition (#18129) 2025-05-16 13:25:28 +02:00
expr_subscript.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_t_string.rs Implement template strings (#17851) 2025-05-30 15:00:56 -05:00
expr_tuple.rs Remove parentheses around multiple exception types on Python 3.14+ (#20768) 2025-10-14 11:17:45 -04:00
expr_unary_op.rs Fix panic when formatting comments in unary expressions (#21501) 2025-11-18 10:48:14 -05:00
expr_yield.rs Switch to Rust 2024 edition (#18129) 2025-05-16 13:25:28 +02:00
expr_yield_from.rs Move {AnyNodeRef, AstNode} to ruff_python_ast crate root (#8030) 2023-10-18 00:01:18 +00:00
mod.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
operator.rs Split implicit concatenated strings before binary expressions (#7145) 2023-09-08 06:51:26 +00:00
parentheses.rs Enable `--document-private-items` for `ruff_python_formatter` (#21903) 2025-12-11 08:23:10 -05:00