Fix leading comment formatting for lambdas with multiple parameters (#21879)

## Summary

This is a follow-up to #21868. As soon as I started merging #21868 into
#21385, I realized that I had missed a test case with `**kwargs` after
the `*args` parameter. Such a case is supposed to be formatted on one
line like:

```py
# input
(
    lambda
    # comment
    *x,
    **y: x
)

# output
(
    lambda
    # comment
    *x, **y: x
)
```

which you can still see on the
[playground](https://play.ruff.rs/bd88d339-1358-40d2-819f-865bfcb23aef?secondary=Format),
but on `main` after #21868, this was formatted as:

```py
(
    lambda
    # comment
    *x,
    **y: x
)
```

because the leading comment on the first parameter caused the whole
group around the parameters to break.

Instead of making these comments leading comments on the first
parameter, this PR makes them leading comments on the parameters list as
a whole.

## Test Plan

New tests, and I will also try merging this into #21385 _before_ opening
it for review this time.

<hr>

(labeling `internal` since #21868 should not be released before some
kind of fix)
This commit is contained in:
Brent Westbrook 2025-12-09 18:15:12 -05:00 committed by GitHub
parent a9be810c38
commit f3714fd3c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 81 additions and 12 deletions

View File

@ -249,3 +249,25 @@ def a():
x: x:
x x
) )
(
lambda
# comment
*x,
**y: x
)
(
lambda
* # comment 2
x,
**y:
x
)
(
lambda
** # comment 1
x:
x
)

View File

@ -871,7 +871,20 @@ fn handle_parameter_comment<'a>(
CommentPlacement::Default(comment) CommentPlacement::Default(comment)
} }
} else if comment.start() < parameter.name.start() { } else if comment.start() < parameter.name.start() {
CommentPlacement::leading(parameter, comment) // For lambdas, where the parameters cannot be parenthesized and the first parameter thus
// starts at the same position as the parent parameters, mark a comment before the first
// parameter as leading on the parameters rather than the individual parameter to prevent
// the whole parameter list from breaking.
//
// Note that this check is not needed above because lambda parameters cannot have
// annotations.
if let Some(AnyNodeRef::Parameters(parameters)) = comment.enclosing_parent()
&& parameters.start() == parameter.start()
{
CommentPlacement::leading(parameters, comment)
} else {
CommentPlacement::leading(parameter, comment)
}
} else { } else {
CommentPlacement::Default(comment) CommentPlacement::Default(comment)
} }
@ -1835,10 +1848,8 @@ fn handle_lambda_comment<'a>(
// ) // )
// ``` // ```
if comment.start() < parameters.start() { if comment.start() < parameters.start() {
return if let Some(first) = parameters.iter().next() return if comment.line_position().is_own_line() {
&& comment.line_position().is_own_line() CommentPlacement::leading(parameters, comment)
{
CommentPlacement::leading(first.as_parameter(), comment)
} else { } else {
CommentPlacement::dangling(comment.enclosing_node(), comment) CommentPlacement::dangling(comment.enclosing_node(), comment)
}; };

View File

@ -32,8 +32,8 @@ impl FormatNodeRule<ExprLambda> for FormatExprLambda {
.split_at(dangling.partition_point(|comment| comment.end() < parameters.start())); .split_at(dangling.partition_point(|comment| comment.end() < parameters.start()));
if dangling_before_parameters.is_empty() { if dangling_before_parameters.is_empty() {
// If the first parameter has a leading comment, insert a hard line break. This // If the parameters have a leading comment, insert a hard line break. This
// comment is associated as a leading comment on the first parameter: // comment is associated as a leading comment on the parameters:
// //
// ```py // ```py
// ( // (
@ -86,11 +86,7 @@ impl FormatNodeRule<ExprLambda> for FormatExprLambda {
// *x: x // *x: x
// ) // )
// ``` // ```
if parameters if comments.has_leading(&**parameters) {
.iter()
.next()
.is_some_and(|parameter| comments.has_leading(parameter.as_parameter()))
{
hard_line_break().fmt(f)?; hard_line_break().fmt(f)?;
} else { } else {
write!(f, [space()])?; write!(f, [space()])?;

View File

@ -255,6 +255,28 @@ def a():
x: x:
x x
) )
(
lambda
# comment
*x,
**y: x
)
(
lambda
* # comment 2
x,
**y:
x
)
(
lambda
** # comment 1
x:
x
)
``` ```
## Output ## Output
@ -513,4 +535,22 @@ def a():
# comment 2 # comment 2
*x: x *x: x
) )
(
lambda
# comment
*x, **y: x
)
(
lambda
# comment 2
*x, **y: x
)
(
lambda
# comment 1
**x: x
)
``` ```