diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.py index 3677fedb708b54..932cd57c7e1df9 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.py @@ -203,3 +203,5 @@ def f( y: z ) + +lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 2858819c2e1405..c1996f813cfb9f 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -1805,18 +1805,6 @@ fn handle_lambda_comment<'a>( locator: &Locator, ) -> CommentPlacement<'a> { if let Some(parameters) = lambda.parameters.as_deref() { - // Comments between the `lambda` and the parameters are dangling on the lambda: - // ```python - // ( - // lambda # comment - // x: - // y - // ) - // ``` - if comment.start() < parameters.start() { - return CommentPlacement::dangling(comment.enclosing_node(), comment); - } - // Comments between the parameters and the body are dangling on the lambda: // ```python // ( diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index f2487fd6a488a8..1025269baf86e2 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -3,8 +3,10 @@ use ruff_python_ast::AnyNodeRef; use ruff_python_ast::ExprLambda; use ruff_text_size::Ranged; +use crate::builders::parenthesize_if_expands; use crate::comments::{dangling_comments, SourceComment}; -use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; +use crate::expression::has_own_parentheses; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses}; use crate::other::parameters::ParametersParentheses; use crate::prelude::*; @@ -25,7 +27,7 @@ impl FormatNodeRule for FormatExprLambda { write!(f, [token("lambda")])?; if let Some(parameters) = parameters { - // In this context, a dangling comment can either be a comment between the `lambda` the + // In this context, a dangling comment can either be a comment between the `lambda` and the // parameters, or a comment between the parameters and the body. let (dangling_before_parameters, dangling_after_parameters) = dangling .split_at(dangling.partition_point(|comment| comment.end() < parameters.start())); @@ -36,20 +38,30 @@ impl FormatNodeRule for FormatExprLambda { write!(f, [dangling_comments(dangling_before_parameters)])?; } - write!( - f, - [parameters - .format() - .with_options(ParametersParentheses::Never)] - )?; + group(&format_with(|f: &mut PyFormatter| { + if f.context().node_level().is_parenthesized() { + soft_block_indent( + ¶meters + .format() + .with_options(ParametersParentheses::Never), + ) + .fmt(f) + } else { + parameters + .format() + .with_options(ParametersParentheses::Never) + .fmt(f) + }?; - write!(f, [token(":")])?; + token(":").fmt(f)?; - if dangling_after_parameters.is_empty() { - write!(f, [space()])?; - } else { - write!(f, [dangling_comments(dangling_after_parameters)])?; - } + if dangling_after_parameters.is_empty() { + space().fmt(f) + } else { + dangling_comments(dangling_after_parameters).fmt(f) + } + })) + .fmt(f)?; } else { write!(f, [token(":")])?; @@ -61,7 +73,13 @@ impl FormatNodeRule for FormatExprLambda { } } - write!(f, [body.format()]) + if has_own_parentheses(body, f.context()).is_some() + || body.needs_parentheses(item.into(), f.context()) == OptionalParentheses::Always + { + body.format().fmt(f) + } else { + parenthesize_if_expands(&body.format().with_options(Parentheses::Never)).fmt(f) + } } fn fmt_dangling_comments( diff --git a/crates/ruff_python_formatter/src/expression/expr_named_expr.rs b/crates/ruff_python_formatter/src/expression/expr_named_expr.rs index 3fe831c3a0b9ad..aaf71fcbfa7102 100644 --- a/crates/ruff_python_formatter/src/expression/expr_named_expr.rs +++ b/crates/ruff_python_formatter/src/expression/expr_named_expr.rs @@ -69,6 +69,7 @@ impl NeedsParentheses for ExprNamedExpr { || parent.is_stmt_delete() || parent.is_stmt_for() || parent.is_stmt_function_def() + || parent.is_expr_lambda() { OptionalParentheses::Always } else { diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index 7697789fe072c0..0ee3d1963795f3 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -206,14 +206,7 @@ if True: #[test] fn quick_test() { let source = r#" -def main() -> None: - if True: - some_very_long_variable_name_abcdefghijk = Foo() - some_very_long_variable_name_abcdefghijk = some_very_long_variable_name_abcdefghijk[ - some_very_long_variable_name_abcdefghijk.some_very_long_attribute_name - == "This is a very long string abcdefghijk" - ] - +lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d "#; let source_type = PySourceType::Python; let (tokens, comment_ranges) = tokens_and_ranges(source, source_type).unwrap(); diff --git a/crates/ruff_python_formatter/src/other/parameters.rs b/crates/ruff_python_formatter/src/other/parameters.rs index 5e67e579de7288..ebc09266b091ba 100644 --- a/crates/ruff_python_formatter/src/other/parameters.rs +++ b/crates/ruff_python_formatter/src/other/parameters.rs @@ -102,7 +102,15 @@ impl FormatNodeRule for FormatParameters { dangling.split_at(parenthesis_comments_end); let format_inner = format_with(|f: &mut PyFormatter| { - let separator = format_with(|f| write!(f, [token(","), soft_line_break_or_space()])); + let separator = format_with(|f: &mut PyFormatter| { + token(",").fmt(f)?; + + if f.context().node_level().is_parenthesized() { + soft_line_break_or_space().fmt(f) + } else { + space().fmt(f) + } + }); let mut joiner = f.join_with(separator); let mut last_node: Option = None; @@ -232,8 +240,6 @@ impl FormatNodeRule for FormatParameters { Ok(()) }); - let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f); - let num_parameters = posonlyargs.len() + args.len() + usize::from(vararg.is_some()) @@ -241,11 +247,14 @@ impl FormatNodeRule for FormatParameters { + usize::from(kwarg.is_some()); if self.parentheses == ParametersParentheses::Never { - write!(f, [group(&format_inner), dangling_comments(dangling)]) + write!(f, [format_inner, dangling_comments(dangling)]) } else if num_parameters == 0 { + let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f); // No parameters, format any dangling comments between `()` write!(f, [empty_parenthesized("(", dangling, ")")]) } else { + let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f); + // Intentionally avoid `parenthesized`, which groups the entire formatted contents. // We want parameters to be grouped alongside return types, one level up, so we // format them "inline" here. 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 8f615a65114234..b8375afafb32e5 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 @@ -209,6 +209,8 @@ lambda: ( # comment y: z ) + +lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d ``` ## Output @@ -242,30 +244,10 @@ lambda x: lambda y: lambda z: (x, y, z) # Trailing # Trailing # Leading -lambda x: lambda y: lambda z: ( - x, - y, - y, - y, - y, - y, - y, - y, - y, - y, - y, - y, - y, - y, - y, - y, - y, - y, - y, - y, - y, - y, - z, +lambda x: ( + lambda y: ( + lambda z: (x, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, y, z) + ) ) # Trailing # Trailing @@ -275,8 +257,10 @@ a = ( ) a = ( - lambda x, # Dangling - y: 1 + lambda + x, # Dangling + y + : 1 ) # Regression test: lambda empty arguments ranges were too long, leading to unstable @@ -289,7 +273,9 @@ a = ( # lambda arguments don't have parentheses, so we never add a magic trailing comma ... def f( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = lambda x: y, + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = lambda + x + : y, ): pass @@ -336,36 +322,42 @@ lambda a, /, c: a ) ( - lambda - # comment - *x: x + lambda + # comment + *x + : x ) ( - lambda - # comment 1 - # comment 2 - *x: + lambda + # comment 1 + # comment 2 + *x + : # comment 3 x ) ( - lambda # comment 1 - # comment 2 - *x: # comment 3 + lambda + # comment 1 + # comment 2 + *x + : # comment 3 x ) lambda *x: x ( - lambda - # comment - *x: x + lambda + # comment + *x + : x ) -lambda: ( # comment +lambda: ( + # comment x ) @@ -393,15 +385,18 @@ lambda: ( # comment ( lambda: # comment - ( # comment + ( + # comment x ) ) ( - lambda # 1 - # 2 - x: # 3 + lambda + # 1 + # 2 + x + : # 3 # 4 # 5 # 6 @@ -409,10 +404,16 @@ lambda: ( # comment ) ( - lambda x, - # comment - y: z + lambda + x, + # comment + y + : z ) + +lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( + *args, **kwargs +), e=1, f=2, g=2: d ```