From 0bec5c0362b553047506bdd9e70b0c45420ef059 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Tue, 9 Dec 2025 14:07:48 -0500 Subject: [PATCH] Fix comment placement in lambda parameters (#21868) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary -- This PR makes two changes to comment placement in lambda parameters. First, we now insert a line break if the first parameter has a leading comment: ```py # input ( lambda * # comment 2 x: x ) # main ( lambda # comment 2 *x: x ) # this PR ( lambda # comment 2 *x: x ) ``` Note the missing space in the output from main. This case is currently unstable on main. Also note that the new formatting is more consistent with our stable formatting in cases where the lambda has its own dangling comment: ```py # input ( lambda # comment 1 * # comment 2 x: x ) # output ( lambda # comment 1 # comment 2 *x: x ) ``` and when a parameter without a comment precedes the split `*x`: ```py # input ( lambda y, * # comment 2 x: x ) # output ( lambda y, # comment 2 *x: x ) ``` This does change the stable formatting, but I think such cases are rare (expecting zero hits in the ecosystem report), this fixes an existing instability, and it should not change any code we've previously formatted. Second, this PR modifies the comment placement such that `# comment 2` in these outputs is still a leading comment on the parameter. This is also not the case on main, where it becomes a [dangling lambda comment](https://play.ruff.rs/3b29bb7e-70e4-4365-88e0-e60fe1857a35?secondary=Comments). This doesn't cause any instability that I'm aware of on main, but it does cause problems when trying to adjust the placement of dangling lambda comments in #21385. Changing the placement in this way should not affect any formatting here. Test Plan -- New lambda tests, plus existing tests covering the cases above with multiple comments around the parameters (see lambda.py 122-143, and 122-205 or so more broadly) I also checked manually that the comments are now leading on the parameter: ```shell ❯ cargo run --bin ruff_python_formatter -- --emit stdout --target-version 3.10 --print-comments <( source: &str, ) -> CommentPlacement<'a> { if let Some(parameters) = lambda.parameters.as_deref() { - // Comments between the `lambda` and the parameters are dangling on the lambda: + // End-of-line comments between the `lambda` and the parameters are dangling on the lambda: // ```python // ( // lambda # comment @@ -1824,8 +1824,24 @@ fn handle_lambda_comment<'a>( // y // ) // ``` + // + // But own-line comments are leading on the first parameter, if it exists: + // ```python + // ( + // lambda + // # comment + // x: + // y + // ) + // ``` if comment.start() < parameters.start() { - return CommentPlacement::dangling(comment.enclosing_node(), comment); + return if let Some(first) = parameters.iter().next() + && comment.line_position().is_own_line() + { + CommentPlacement::leading(first.as_parameter(), comment) + } else { + CommentPlacement::dangling(comment.enclosing_node(), comment) + }; } // Comments between the parameters and the body are dangling on the lambda: diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index c5890fba24..335f112323 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -32,7 +32,69 @@ impl FormatNodeRule for FormatExprLambda { .split_at(dangling.partition_point(|comment| comment.end() < parameters.start())); if dangling_before_parameters.is_empty() { - write!(f, [space()])?; + // If the first parameter has a leading comment, insert a hard line break. This + // comment is associated as a leading comment on the first parameter: + // + // ```py + // ( + // lambda + // * # comment + // x: + // x + // ) + // ``` + // + // so a hard line break is needed to avoid formatting it like: + // + // ```py + // ( + // lambda # comment + // *x: x + // ) + // ``` + // + // which is unstable because it's missing the second space before the comment. + // + // Inserting the line break causes it to format like: + // + // ```py + // ( + // lambda + // # comment + // *x :x + // ) + // ``` + // + // which is also consistent with the formatting in the presence of an actual + // dangling comment on the lambda: + // + // ```py + // ( + // lambda # comment 1 + // * # comment 2 + // x: + // x + // ) + // ``` + // + // formats to: + // + // ```py + // ( + // lambda # comment 1 + // # comment 2 + // *x: x + // ) + // ``` + if parameters + .iter() + .next() + .is_some_and(|parameter| comments.has_leading(parameter.as_parameter())) + { + hard_line_break().fmt(f)?; + } else { + write!(f, [space()])?; + } } else { write!(f, [dangling_comments(dangling_before_parameters)])?; } diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda.py.snap index 03332c6f92..3009dfaefc 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.py -snapshot_kind: text --- ## Input ```python @@ -235,6 +234,27 @@ def a(): g = 10 ) +( + lambda + * # comment 2 + x: + x +) + +( + lambda # comment 1 + * # comment 2 + x: + x +) + +( + lambda # comment 1 + y, + * # comment 2 + x: + x +) ``` ## Output @@ -473,4 +493,24 @@ def a(): g=2: d, g=10, ) + + +( + lambda + # comment 2 + *x: x +) + +( + lambda # comment 1 + # comment 2 + *x: x +) + +( + lambda # comment 1 + y, + # comment 2 + *x: x +) ```