diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index 71ff3f98d3..c7d9a66aee 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -1,14 +1,11 @@ -use ruff_formatter::{FormatRuleWithOptions, RemoveSoftLinesBuffer, write}; +use ruff_formatter::{FormatRuleWithOptions, RemoveSoftLinesBuffer, format_args, write}; use ruff_python_ast::{AnyNodeRef, Expr, ExprLambda}; use ruff_text_size::Ranged; use crate::builders::parenthesize_if_expands; use crate::comments::dangling_comments; use crate::expression::has_own_parentheses; -use crate::expression::maybe_parenthesize_expression; -use crate::expression::parentheses::{ - NeedsParentheses, OptionalParentheses, Parenthesize, is_expression_parenthesized, -}; +use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses}; use crate::other::parameters::ParametersParentheses; use crate::prelude::*; use crate::preview::is_force_single_line_lambda_parameters_enabled; @@ -83,28 +80,32 @@ impl FormatNodeRule for FormatExprLambda { } } - // Avoid parenthesizing lists, dictionaries, etc. that have their own parentheses, but still - // wrap calls and subscripts, which can have long expressions before the parentheses: - // ```py - // lambda arg1, arg2, arg3, *args, **kwargs: a_loooooooooooong_call_expression.with_an_attr(inner, args) - // ``` - let needs_parentheses = has_own_parentheses(body, f.context()).is_none() - || matches!(&**body, Expr::Call(_) | Expr::Subscript(_)); + if is_parenthesize_lambda_bodies_enabled(f.context()) { + let fmt_body = format_with(|f| { + if matches!(&**body, Expr::Call(_) | Expr::Subscript(_)) { + let body = body.format().with_options(Parentheses::Never).memoized(); - if is_parenthesize_lambda_bodies_enabled(f.context()) - && needs_parentheses - && !is_expression_parenthesized(body.into(), comments.ranges(), f.context().source()) - { - match self.layout { - ExprLambdaLayout::Default => maybe_parenthesize_expression( - body, - item, - Parenthesize::IfBreaksParenthesizedNested, - ) - .fmt(f), - ExprLambdaLayout::Assignment => { - fits_expanded(&parenthesize_if_expands(&body.format())).fmt(f) + best_fitting![ + // body all flat + body, + // body expanded + body, + // parenthesized + format_args![token("("), block_indent(&body), token(")")] + ] + .fmt(f) + } else if has_own_parentheses(body, f.context()).is_some() { + // We probably need to be more careful here and preserve parentheses if there are comments? + body.format().fmt(f) + } else { + parenthesize_if_expands(&body.format().with_options(Parentheses::Never)).fmt(f) } + }); + + match self.layout { + // Can we move the `fits_expanded` into the assignment formatting? + ExprLambdaLayout::Assignment => fits_expanded(&fmt_body).fmt(f), + ExprLambdaLayout::Default => fmt_body.fmt(f), } } else { body.format().fmt(f) diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_multiline_strings.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_multiline_strings.py.snap index df7f234dd6..29af6e1b07 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_multiline_strings.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_multiline_strings.py.snap @@ -375,7 +375,7 @@ a = b if """ # Another use case data = yaml.load("""\ a: 1 -@@ -77,19 +106,23 @@ +@@ -77,10 +106,12 @@ b: 2 """, ) @@ -390,19 +390,7 @@ a = b if """ MULTILINE = """ foo - """.replace("\n", "") --generated_readme = lambda project_name: """ -+generated_readme = lambda project_name: ( -+ """ - {} - - - """.strip().format(project_name) -+) - parser.usage += """ - Custom extra help summary. - -@@ -156,16 +189,24 @@ +@@ -156,16 +187,24 @@ 10 LOAD_CONST 0 (None) 12 RETURN_VALUE """ % (_C.__init__.__code__.co_firstlineno + 1,) @@ -433,7 +421,7 @@ a = b if """ [ """cow moos""", -@@ -206,7 +247,9 @@ +@@ -206,7 +245,9 @@ "c" ) @@ -444,7 +432,7 @@ a = b if """ assert some_var == expected_result, """ test -@@ -224,10 +267,8 @@ +@@ -224,10 +265,8 @@ """Sxxxxxxx xxxxxxxx, xxxxxxx xx xxxxxxxxx xxxxxxxxxxxxx xxxxxxx xxxxxxxxx xxx-xxxxxxxxxx xxxxxx xx xxx-xxxxxx""" ), @@ -457,7 +445,7 @@ a = b if """ }, } -@@ -246,14 +287,12 @@ +@@ -246,14 +285,12 @@ a a""" ), @@ -597,13 +585,11 @@ data = yaml.load( MULTILINE = """ foo """.replace("\n", "") -generated_readme = lambda project_name: ( - """ +generated_readme = lambda project_name: """ {} """.strip().format(project_name) -) parser.usage += """ Custom extra help summary. 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 36baf2fc49..ec91dd0e1a 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 @@ -913,7 +913,27 @@ if 1: ): pass -@@ -218,71 +200,79 @@ +@@ -156,7 +138,8 @@ + *x: x + ) + +-lambda: ( # comment ++lambda: ( ++ # comment + x + ) + +@@ -184,7 +167,8 @@ + + ( + lambda: # comment +- ( # comment ++ ( ++ # comment + x + ) + ) +@@ -218,71 +202,79 @@ # Leading lambda x: ( @@ -1052,7 +1072,7 @@ if 1: # Regression tests for https://github.com/astral-sh/ruff/issues/8179 -@@ -291,9 +281,9 @@ +@@ -291,9 +283,9 @@ c, d, e, @@ -1065,7 +1085,7 @@ if 1: ) -@@ -302,15 +292,9 @@ +@@ -302,15 +294,9 @@ c, d, e, @@ -1084,7 +1104,7 @@ if 1: g=10, ) -@@ -320,9 +304,9 @@ +@@ -320,9 +306,9 @@ c, d, e, @@ -1097,7 +1117,7 @@ if 1: ) -@@ -338,9 +322,9 @@ +@@ -338,9 +324,9 @@ class C: function_dict: Dict[Text, Callable[[CRFToken], Any]] = { @@ -1110,7 +1130,7 @@ if 1: } -@@ -352,42 +336,40 @@ +@@ -352,42 +338,40 @@ def foo(): if True: if True: @@ -1169,7 +1189,7 @@ if 1: CREATE TABLE {table} AS SELECT ROW_NUMBER() OVER () AS id, {var} FROM ( -@@ -408,12 +390,12 @@ +@@ -408,12 +392,12 @@ # 6 ) @@ -1186,7 +1206,7 @@ if 1: ) very_long_variable_name_x, very_long_variable_name_y = ( -@@ -421,8 +403,8 @@ +@@ -421,8 +405,8 @@ lambda b: b * another_very_long_expression_here, ) @@ -1197,19 +1217,4 @@ if 1: x, more_args, additional_parameters ) ) -@@ -433,9 +415,11 @@ - if 3: - if self.location in EVM_EVMLIKE_LOCATIONS and database is not None: - exported_dict["notes"] = EVM_ADDRESS_REGEX.sub( -- repl=lambda matched_address: self._maybe_add_label_with_address( -- database=database, -- matched_address=matched_address, -+ repl=lambda matched_address: ( -+ self._maybe_add_label_with_address( -+ database=database, -+ matched_address=matched_address, -+ ) - ), - string=exported_dict["notes"], - ) ``` diff --git a/crates/ruff_python_formatter/tests/snapshots/format@multiline_string_deviations.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@multiline_string_deviations.py.snap index 40a43052b4..9504e52ae8 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@multiline_string_deviations.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@multiline_string_deviations.py.snap @@ -111,15 +111,16 @@ generated_readme = ( ```diff --- Stable +++ Preview -@@ -44,8 +44,8 @@ +@@ -44,10 +44,8 @@ # this by changing `Lambda::needs_parentheses` to return `BestFit` but it causes # issues when the lambda has comments. # Let's keep this as a known deviation for now. -generated_readme = ( - lambda project_name: """ -+generated_readme = lambda project_name: ( -+ """ ++generated_readme = lambda project_name: """ {} + """.strip().format(project_name) +-) ```