From 1b586430402d6fd660102284829a411440966476 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Thu, 20 Nov 2025 15:12:22 -0500 Subject: [PATCH] wip: parenthesize long lambda bodies --- crates/ruff_python_formatter/src/builders.rs | 24 +++-- .../src/expression/expr_lambda.rs | 18 +++- crates/ruff_python_formatter/src/preview.rs | 7 ++ ...bility@cases__preview_long_strings.py.snap | 9 +- .../format@expression__lambda.py.snap | 89 ++++++++++++++++--- 5 files changed, 123 insertions(+), 24 deletions(-) diff --git a/crates/ruff_python_formatter/src/builders.rs b/crates/ruff_python_formatter/src/builders.rs index 8d7aeb502b..f075ccd611 100644 --- a/crates/ruff_python_formatter/src/builders.rs +++ b/crates/ruff_python_formatter/src/builders.rs @@ -1,4 +1,4 @@ -use ruff_formatter::{Argument, Arguments, write}; +use ruff_formatter::{Argument, Arguments, format_args, write}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::context::{NodeLevel, WithNodeLevel}; @@ -31,20 +31,32 @@ impl ParenthesizeIfExpands<'_, '_> { impl<'ast> Format> for ParenthesizeIfExpands<'_, 'ast> { fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { { + let parens_id = f.group_id("optional_parentheses"); + let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f); write!( f, [group(&format_with(|f| { - if_group_breaks(&token("(")).fmt(f)?; - if self.indent { - soft_block_indent(&Arguments::from(&self.inner)).fmt(f)?; + write!( + f, + [group(&format_args![ + if_group_breaks(&token("(")), + indent_if_group_breaks( + &format_args![soft_line_break(), &Arguments::from(&self.inner)], + parens_id + ), + soft_line_break(), + if_group_breaks(&token(")")) + ]) + .with_id(Some(parens_id))] + ) } else { + if_group_breaks(&token("(")).fmt(f)?; Arguments::from(&self.inner).fmt(f)?; + if_group_breaks(&token(")")).fmt(f) } - - if_group_breaks(&token(")")).fmt(f) }))] ) } diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index 418db4641a..035e866420 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -4,10 +4,14 @@ use ruff_python_ast::ExprLambda; use ruff_text_size::Ranged; use crate::comments::dangling_comments; -use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; +use crate::expression::has_own_parentheses; +use crate::expression::parentheses::{ + NeedsParentheses, OptionalParentheses, is_expression_parenthesized, optional_parentheses, +}; 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; @@ -27,7 +31,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())); @@ -76,7 +80,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() + && !is_expression_parenthesized(body.into(), comments.ranges(), f.context().source()) + { + fits_expanded(&optional_parentheses(&body.format())).fmt(f) + } else { + body.format().fmt(f) + } } } diff --git a/crates/ruff_python_formatter/src/preview.rs b/crates/ruff_python_formatter/src/preview.rs index 9e95ad210b..fe55eef9a0 100644 --- a/crates/ruff_python_formatter/src/preview.rs +++ b/crates/ruff_python_formatter/src/preview.rs @@ -53,6 +53,13 @@ pub(crate) const fn is_avoid_parens_for_long_as_captures_enabled( 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. 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..ca59cec651 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 @@ -906,11 +906,10 @@ x = { -) +string_with_escaped_nameescape = "........................................................................... \\N{LAO KO LA}" --msg = lambda x: ( + msg = lambda 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" ++ f"this is a very very very very long lambda value {x} that doesn't fit on a single line" ) dict_with_lambda_values = { @@ -1403,8 +1402,8 @@ 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" +msg = 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 07d9e9e135..3639222ef3 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 @@ -682,18 +682,86 @@ class C: ```diff --- Stable +++ Preview -@@ -280,9 +280,7 @@ +@@ -74,7 +74,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,29 +220,31 @@ + + # 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 + + +@@ -280,9 +284,9 @@ ] # 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 ++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 +289,9 @@ +@@ -291,9 +295,9 @@ c, d, e, @@ -706,7 +774,7 @@ class C: ) -@@ -302,15 +300,7 @@ +@@ -302,15 +306,9 @@ c, d, e, @@ -719,21 +787,22 @@ class C: - e=1, - f=2, - g=2: d, -+ 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, ) -@@ -320,9 +310,10 @@ +@@ -320,9 +318,9 @@ c, d, e, - f=lambda self, - *args, - **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1, -+ f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( -+ *args, **kwargs -+ ) -+ + 1, ++ f=lambda self, *args, **kwargs: ( ++ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1 ++ ), )