Preserve required parentheses in lambda bodies (#22747)

Summary
--

This PR fixes the issues revealed in #22744 by adding an additional
branch to
the lambda body formatting that checks if the body `needs_parentheses`
before
falling back on the `Parentheses::Never` case. I also updated the
`ExprNamed::needs_parentheses` implementation to match the one from
#8465.

Test Plan
--

New test based on the failing cases in #22744. I also checked out #22744
and
checked that the tests pass after applying the changes from this PR.
This commit is contained in:
Brent Westbrook
2026-01-20 10:28:29 -05:00
committed by GitHub
parent c1491a7a72
commit 5c441523e3
4 changed files with 47 additions and 1 deletions

View File

@@ -804,3 +804,12 @@ transform = lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date).betwe
x
)
)
lambda x: (
x := 1
)
(
lambda # dangling header comment
: (x := 1)
)

View File

@@ -145,6 +145,7 @@ impl FormatNodeRule<ExprLambda> for FormatExprLambda {
let fmt_body = FormatBody {
body,
dangling_header_comments,
needs_parentheses: body.needs_parentheses(item.into(), f.context()),
};
match self.layout {
@@ -262,6 +263,7 @@ struct FormatBody<'a> {
/// )
/// ```
dangling_header_comments: &'a [SourceComment],
needs_parentheses: OptionalParentheses,
}
impl Format<PyFormatContext<'_>> for FormatBody<'_> {
@@ -269,6 +271,7 @@ impl Format<PyFormatContext<'_>> for FormatBody<'_> {
let FormatBody {
dangling_header_comments,
body,
needs_parentheses,
} = self;
let body = *body;
@@ -438,6 +441,14 @@ impl Format<PyFormatContext<'_>> for FormatBody<'_> {
else if has_own_parentheses(body, f.context()).is_some() {
body.format().fmt(f)
}
// Include parentheses for cases that always require them, such as named expressions:
//
// ```py
// lambda x: (y := x + 1)
// ```
else if matches!(needs_parentheses, OptionalParentheses::Always) {
body.format().with_options(Parentheses::Always).fmt(f)
}
// Finally, for expressions without their own parentheses, use
// `parenthesize_if_expands` to add parentheses around the body, only if it expands
// across multiple lines. The `Parentheses::Never` here also removes unnecessary

View File

@@ -66,6 +66,7 @@ impl NeedsParentheses for ExprNamed {
|| parent.is_stmt_delete()
|| parent.is_stmt_for()
|| parent.is_stmt_function_def()
|| parent.is_expr_lambda()
{
OptionalParentheses::Always
} else {

View File

@@ -1,6 +1,5 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.py
---
## Input
```python
@@ -810,6 +809,15 @@ transform = lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date).betwe
x
)
)
lambda x: (
x := 1
)
(
lambda # dangling header comment
: (x := 1)
)
```
## Output
@@ -1649,6 +1657,13 @@ transform = (
x
)
)
lambda x: (x := 1)
(
lambda: # dangling header comment
(x := 1)
)
```
@@ -2567,4 +2582,14 @@ transform = (
x
)
)
@@ -837,6 +854,7 @@
lambda x: (x := 1)
(
- lambda: # dangling header comment
- (x := 1)
+ lambda: ( # dangling header comment
+ x := 1
+ )
)
```