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 4a7090ff13..017d1124c9 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 @@ -125,6 +125,13 @@ lambda a, /, c: a *x: x ) +( + lambda + # comment + *x, + **y: x +) + ( lambda # comment 1 @@ -196,6 +203,17 @@ lambda: ( # comment x ) +( + lambda # 1 + # 2 + x, # 3 + # 4 + y + : # 5 + # 6 + x +) + ( lambda x, @@ -204,6 +222,71 @@ lambda: ( # comment z ) + +# 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 # 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, + y, + y, + y, + y, + y, + y, + y, + y, + z +] # Trailing +# Trailing + lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d # Regression tests for https://github.com/astral-sh/ruff/issues/8179 diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index c5890fba24..bec702ea52 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -1,12 +1,16 @@ +use ruff_formatter::RemoveSoftLinesBuffer; use ruff_formatter::write; use ruff_python_ast::AnyNodeRef; use ruff_python_ast::ExprLambda; use ruff_text_size::Ranged; use crate::comments::dangling_comments; -use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parenthesize}; +use crate::expression::{has_own_parentheses, maybe_parenthesize_expression}; use crate::other::parameters::ParametersParentheses; use crate::prelude::*; +use crate::preview::is_force_single_line_lambda_parameters_enabled; +use crate::preview::is_parenthesize_lambda_bodies_enabled; #[derive(Default)] pub struct FormatExprLambda; @@ -26,7 +30,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())); @@ -37,12 +41,25 @@ impl FormatNodeRule for FormatExprLambda { write!(f, [dangling_comments(dangling_before_parameters)])?; } - write!( - f, - [parameters - .format() - .with_options(ParametersParentheses::Never)] - )?; + // Try to keep the parameters on a single line, unless there are intervening comments. + if is_force_single_line_lambda_parameters_enabled(f.context()) + && !comments.contains_comments(parameters.as_ref().into()) + { + let mut buffer = RemoveSoftLinesBuffer::new(f); + write!( + buffer, + [parameters + .format() + .with_options(ParametersParentheses::Never)] + )?; + } else { + write!( + f, + [parameters + .format() + .with_options(ParametersParentheses::Never)] + )?; + } write!(f, [token(":")])?; @@ -62,7 +79,15 @@ impl FormatNodeRule for FormatExprLambda { } } - write!(f, [body.format()]) + // Avoid parenthesizing lists, dictionaries, etc. + if is_parenthesize_lambda_bodies_enabled(f.context()) + && has_own_parentheses(body, f.context()).is_none() + { + maybe_parenthesize_expression(body, item, Parenthesize::IfBreaksParenthesizedNested) + .fmt(f) + } else { + body.format().fmt(f) + } } } diff --git a/crates/ruff_python_formatter/src/expression/expr_named.rs b/crates/ruff_python_formatter/src/expression/expr_named.rs index 117e42825e..a983a0c160 100644 --- a/crates/ruff_python_formatter/src/expression/expr_named.rs +++ b/crates/ruff_python_formatter/src/expression/expr_named.rs @@ -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 { diff --git a/crates/ruff_python_formatter/src/preview.rs b/crates/ruff_python_formatter/src/preview.rs index 9d307390d6..fe55eef9a0 100644 --- a/crates/ruff_python_formatter/src/preview.rs +++ b/crates/ruff_python_formatter/src/preview.rs @@ -52,3 +52,19 @@ pub(crate) const fn is_avoid_parens_for_long_as_captures_enabled( ) -> bool { context.is_preview() } + +/// Returns `true` if the +/// [`parenthesize_lambda_bodies`](https://github.com/astral-sh/ruff/pull/21385) preview style is +/// enabled. +pub(crate) const fn is_parenthesize_lambda_bodies_enabled(context: &PyFormatContext) -> bool { + context.is_preview() +} + +/// Returns `true` if the +/// [`force_single_line_lambda_parameters`](https://github.com/astral-sh/ruff/pull/21385) preview +/// style is enabled. +pub(crate) const fn is_force_single_line_lambda_parameters_enabled( + context: &PyFormatContext, +) -> bool { + context.is_preview() +} diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_long_strings.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_long_strings.py.snap index 7b36236110..41cd04de61 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_long_strings.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_long_strings.py.snap @@ -854,7 +854,7 @@ x = { long_unmergable_string_with_pragma = ( "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore -@@ -468,49 +358,24 @@ +@@ -468,49 +358,26 @@ " of it." ) @@ -910,11 +910,13 @@ x = { - f"this is a very very very very long lambda value {x} that doesn't fit on a" - " single line" +msg = ( -+ lambda x: f"this is a very very very very long lambda value {x} that doesn't fit on a single line" ++ lambda x: ( ++ f"this is a very very very very long lambda value {x} that doesn't fit on a single line" ++ ) ) dict_with_lambda_values = { -@@ -522,65 +387,58 @@ +@@ -522,65 +389,58 @@ # Complex string concatenations with a method call in the middle. code = ( @@ -998,7 +1000,7 @@ x = { ) log.info( -@@ -588,7 +446,7 @@ +@@ -588,7 +448,7 @@ ) log.info( @@ -1007,7 +1009,7 @@ x = { ) x = { -@@ -597,10 +455,10 @@ +@@ -597,10 +457,10 @@ ) } x = { @@ -1404,7 +1406,9 @@ string_with_escaped_nameescape = ".............................................. string_with_escaped_nameescape = "........................................................................... \\N{LAO KO LA}" msg = ( - lambda x: f"this is a very very very very long lambda value {x} that doesn't fit on a single line" + lambda x: ( + f"this is a very very very very long lambda value {x} that doesn't fit on a single line" + ) ) dict_with_lambda_values = { 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..df5de237b2 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 @@ -132,6 +131,13 @@ lambda a, /, c: a *x: x ) +( + lambda + # comment + *x, + **y: x +) + ( lambda # comment 1 @@ -203,6 +209,17 @@ lambda: ( # comment x ) +( + lambda # 1 + # 2 + x, # 3 + # 4 + y + : # 5 + # 6 + x +) + ( lambda x, @@ -211,6 +228,71 @@ lambda: ( # comment z ) + +# 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 # 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, + y, + y, + y, + y, + y, + y, + y, + y, + z +] # Trailing +# Trailing + lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d # Regression tests for https://github.com/astral-sh/ruff/issues/8179 @@ -367,6 +449,12 @@ lambda a, /, c: a *x: x ) +( + lambda + # comment + *x, **y: x +) + ( lambda # comment 1 @@ -434,12 +522,87 @@ lambda: ( # comment x ) +( + lambda # 1 + # 2 + x, # 3 + # 4 + y: # 5 + # 6 + x +) + ( lambda x, # comment y: z ) + +# 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 # 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, + y, + y, + y, + y, + y, + y, + y, + y, + z, +] # Trailing +# Trailing + lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( *args, **kwargs ), e=1, f=2, g=2: d @@ -474,3 +637,226 @@ def a(): g=10, ) ``` + + +## Preview changes +```diff +--- Stable ++++ Preview +@@ -27,30 +27,10 @@ + # 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 + +@@ -74,7 +54,9 @@ + + # 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 + +@@ -218,71 +200,79 @@ + + # 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 # Trailing ++ 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 + + + # 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, +- y, +- y, +- y, +- y, +- y, +- y, +- y, +- y, +- z, +-] # Trailing ++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, ++ y, ++ y, ++ y, ++ y, ++ y, ++ y, ++ y, ++ y, ++ z, ++ ] ++ ) ++) # Trailing + # Trailing + +-lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( +- *args, **kwargs +-), e=1, f=2, g=2: d ++lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: ( ++ d ++) + + + # Regression tests for https://github.com/astral-sh/ruff/issues/8179 +@@ -291,9 +281,9 @@ + c, + d, + e, +- f=lambda self, +- *args, +- **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), ++ f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( ++ *args, **kwargs ++ ), + ) + + +@@ -302,14 +292,8 @@ + c, + d, + e, +- f=lambda self, +- araa, +- kkkwargs, +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, +- args, +- kwargs, +- e=1, +- f=2, +- g=2: d, ++ f=lambda self, araa, kkkwargs, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, args, kwargs, e=1, f=2, g=2: ( ++ d ++ ), + g=10, + ) +```