From 52e9222bb6675a30a301a8e919009d8aee2a9d8a Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 11 Nov 2025 13:00:43 -0500 Subject: [PATCH 01/13] tests --- .../ruff/expression/lambda.options.json | 8 + .../test/fixtures/ruff/expression/lambda.py | 102 +++ .../format@expression__lambda.py.snap | 644 +++++++++++++++++- 3 files changed, 724 insertions(+), 30 deletions(-) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.options.json diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.options.json b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.options.json new file mode 100644 index 0000000000..92e14f0a45 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.options.json @@ -0,0 +1,8 @@ +[ + { + "preview": "disabled" + }, + { + "preview": "enabled" + } +] 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..41007d3381 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 @@ -135,6 +142,17 @@ lambda a, /, c: a x ) +( + lambda + # comment 1 + * + # comment 2 + x, + **y: + # comment 3 + x +) + ( lambda # comment 1 * # comment 2 @@ -142,6 +160,14 @@ lambda a, /, c: a x ) +( + lambda # comment 1 + * # comment 2 + x, + y: # comment 3 + x +) + lambda *x\ :x @@ -196,6 +222,17 @@ lambda: ( # comment x ) +( + lambda # 1 + # 2 + x, # 3 + # 4 + y + : # 5 + # 6 + x +) + ( lambda x, @@ -204,6 +241,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/tests/snapshots/format@expression__lambda.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda.py.snap index 03332c6f92..6e40c8072b 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 @@ -132,6 +132,13 @@ lambda a, /, c: a *x: x ) +( + lambda + # comment + *x, + **y: x +) + ( lambda # comment 1 @@ -142,6 +149,17 @@ lambda a, /, c: a x ) +( + lambda + # comment 1 + * + # comment 2 + x, + **y: + # comment 3 + x +) + ( lambda # comment 1 * # comment 2 @@ -149,6 +167,14 @@ lambda a, /, c: a x ) +( + lambda # comment 1 + * # comment 2 + x, + y: # comment 3 + x +) + lambda *x\ :x @@ -203,6 +229,17 @@ lambda: ( # comment x ) +( + lambda # 1 + # 2 + x, # 3 + # 4 + y + : # 5 + # 6 + x +) + ( lambda x, @@ -211,6 +248,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 @@ -237,8 +339,18 @@ def a(): ``` -## Output -```python +## Outputs +### Output 1 +``` +indent-style = space +line-width = 88 +indent-width = 4 +quote-style = Double +magic-trailing-comma = Respect +preview = Disabled +``` + +```py # Leading lambda x: x # Trailing # Trailing @@ -301,8 +413,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 @@ -363,23 +477,54 @@ lambda a, /, c: a ( lambda - # comment - *x: x + # comment + *x + : x ) ( lambda - # comment 1 - # comment 2 - *x: + # comment + *x, + **y + : x +) + +( + lambda + # comment 1 + # comment 2 + *x + : + # comment 3 + x +) + +( + lambda + # comment 1 + # comment 2 + *x, + **y + : # comment 3 x ) ( lambda # comment 1 - # comment 2 - *x: # comment 3 + # comment 2 + *x + : # comment 3 + x +) + +( + lambda # comment 1 + # comment 2 + *x, + y + : # comment 3 x ) @@ -387,8 +532,9 @@ lambda *x: x ( lambda - # comment - *x: x + # comment + *x + : x ) lambda: ( # comment @@ -426,8 +572,9 @@ lambda: ( # comment ( lambda # 1 - # 2 - x: # 3 + # 2 + x + : # 3 # 4 # 5 # 6 @@ -435,11 +582,89 @@ lambda: ( # comment ) ( - lambda x, - # comment - y: z + 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 @@ -451,9 +676,11 @@ def a(): c, d, e, - f=lambda self, - *args, - **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), + f=lambda + self, + *args, + **kwargs + : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), ) @@ -462,15 +689,372 @@ def a(): 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, + ) +``` + + +### Output 2 +``` +indent-style = space +line-width = 88 +indent-width = 4 +quote-style = Double +magic-trailing-comma = Respect +preview = Enabled +``` + +```py +# Leading +lambda x: x # Trailing +# Trailing + +# Leading +lambda x, y: x # Trailing +# Trailing + +# Leading +lambda x, y: x, y # Trailing +# Trailing + +# Leading +lambda x, /, y: x # Trailing +# Trailing + +# Leading +lambda x: lambda y: lambda z: x # Trailing +# Trailing + +# Leading +lambda x: lambda y: lambda z: (x, y, z) # Trailing +# Trailing + +# Leading +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) + ) +) # Trailing +# Trailing + +a = ( + lambda: # Dangling + 1 +) + +a = ( + lambda + x, # Dangling + y + : 1 +) + +# Regression test: lambda empty arguments ranges were too long, leading to unstable +# formatting +( + lambda: ( # + ), +) + + +# lambda arguments don't have parentheses, so we never add a magic trailing comma ... +def f( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = lambda x: ( + y + ), +): + pass + + +# ...but we do preserve a trailing comma after the arguments +a = lambda b,: 0 + +lambda a,: 0 +lambda *args,: 0 +lambda **kwds,: 0 +lambda a, *args,: 0 +lambda a, **kwds,: 0 +lambda *args, b,: 0 +lambda *, b,: 0 +lambda *args, **kwds,: 0 +lambda a, *args, b,: 0 +lambda a, *, b,: 0 +lambda a, *args, **kwds,: 0 +lambda *args, b, **kwds,: 0 +lambda *, b, **kwds,: 0 +lambda a, *args, b, **kwds,: 0 +lambda a, *, b, **kwds,: 0 +lambda a, /: a +lambda a, /, c: a + +# Dangling comments without parameters. +( + lambda: # 3 + None +) + +( + lambda: + # 3 + None +) + +( + lambda: # 1 + # 2 + # 3 + # 4 + None # 5 +) + +( + lambda + # comment + *x + : x +) + +( + lambda + # comment + *x, + **y + : x +) + +( + lambda + # comment 1 + # comment 2 + *x + : + # comment 3 + x +) + +( + lambda + # comment 1 + # comment 2 + *x, + **y + : + # comment 3 + x +) + +( + lambda # comment 1 + # comment 2 + *x + : # comment 3 + x +) + +( + lambda # comment 1 + # comment 2 + *x, + y + : # comment 3 + x +) + +lambda *x: x + +( + lambda + # comment + *x + : x +) + +lambda: ( # comment + x +) + +( + lambda: # comment + x +) + +( + lambda: + # comment + x +) + +( + lambda: # comment + x +) + +( + lambda: + # comment + x +) + +( + lambda: # comment + ( # comment + x + ) +) + +( + lambda # 1 + # 2 + x + : # 3 + # 4 + # 5 + # 6 + 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 + + +# Regression tests for https://github.com/astral-sh/ruff/issues/8179 +def a(): + return b( + c, + d, + e, + f=lambda + self, + *args, + **kwargs + : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), + ) + + +def a(): + return b( + c, + d, + e, + f=lambda + self, + araa, + kkkwargs, + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, + args, + kwargs, + e=1, + f=2, + g=2 + : d, g=10, ) ``` From f999817a4b1da2225e8795a296b00fa4e9b965b6 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 11 Nov 2025 13:01:20 -0500 Subject: [PATCH 02/13] Indent lambda parameters if parameters wrap Co-authored-by: Micha Reiser --- .../src/expression/expr_lambda.rs | 52 +++++++++++++------ .../src/expression/expr_named.rs | 1 + .../src/other/parameters.rs | 2 +- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index c5890fba24..6918ba9b08 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -1,9 +1,9 @@ -use ruff_formatter::write; +use ruff_formatter::{format_args, write}; use ruff_python_ast::AnyNodeRef; use ruff_python_ast::ExprLambda; use ruff_text_size::Ranged; -use crate::comments::dangling_comments; +use crate::comments::{dangling_comments, leading_comments}; use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; use crate::other::parameters::ParametersParentheses; use crate::prelude::*; @@ -26,31 +26,49 @@ 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())); if dangling_before_parameters.is_empty() { write!(f, [space()])?; - } else { - 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() + && (parameters.len() > 1 || !dangling_before_parameters.is_empty()) + { + let end_of_line_start = dangling_before_parameters + .partition_point(|comment| comment.line_position().is_end_of_line()); + let (same_line_comments, own_line_comments) = + dangling_before_parameters.split_at(end_of_line_start); - write!(f, [token(":")])?; + dangling_comments(same_line_comments).fmt(f)?; - if dangling_after_parameters.is_empty() { - write!(f, [space()])?; - } else { - write!(f, [dangling_comments(dangling_after_parameters)])?; - } + soft_block_indent(&format_args![ + leading_comments(own_line_comments), + parameters + .format() + .with_options(ParametersParentheses::Never), + ]) + .fmt(f) + } else { + parameters + .format() + .with_options(ParametersParentheses::Never) + .fmt(f) + }?; + + token(":").fmt(f)?; + + if dangling_after_parameters.is_empty() { + space().fmt(f) + } else { + dangling_comments(dangling_after_parameters).fmt(f) + } + })) + .fmt(f)?; } else { write!(f, [token(":")])?; 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/other/parameters.rs b/crates/ruff_python_formatter/src/other/parameters.rs index 1c6682bab1..736d0d6fa0 100644 --- a/crates/ruff_python_formatter/src/other/parameters.rs +++ b/crates/ruff_python_formatter/src/other/parameters.rs @@ -241,7 +241,7 @@ impl FormatNodeRule for FormatParameters { let num_parameters = item.len(); 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 `()` From 4f8ba29893993c31a0c406e1dbd7f6dd214a2bfd Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 11 Nov 2025 12:59:34 -0500 Subject: [PATCH 03/13] parenthesize the lambda body if it expands --- crates/ruff_python_formatter/src/context.rs | 4 ++++ .../src/expression/expr_lambda.rs | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/ruff_python_formatter/src/context.rs b/crates/ruff_python_formatter/src/context.rs index 528afc6c71..483f41fece 100644 --- a/crates/ruff_python_formatter/src/context.rs +++ b/crates/ruff_python_formatter/src/context.rs @@ -112,6 +112,10 @@ impl<'a> PyFormatContext<'a> { pub(crate) const fn is_preview(&self) -> bool { self.options.preview().is_enabled() } + + pub(crate) const fn is_stable(&self) -> bool { + !self.is_preview() + } } impl FormatContext for PyFormatContext<'_> { diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index 6918ba9b08..6c7334633c 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -4,7 +4,8 @@ use ruff_python_ast::ExprLambda; use ruff_text_size::Ranged; use crate::comments::{dangling_comments, leading_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::*; @@ -80,7 +81,12 @@ impl FormatNodeRule for FormatExprLambda { } } - write!(f, [body.format()]) + // Avoid parenthesizing lists, dictionaries, etc. + if f.context().is_stable() || has_own_parentheses(body, f.context()).is_some() { + body.format().fmt(f) + } else { + maybe_parenthesize_expression(body, item, Parenthesize::IfBreaksOrIfRequired).fmt(f) + } } } From b75117e8b5838082c0a75a11c50b8d0b3ac53012 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 11 Nov 2025 10:48:45 -0500 Subject: [PATCH 04/13] update parenthesize variant --- crates/ruff_python_formatter/src/expression/expr_lambda.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index 6c7334633c..658bb19ef1 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -85,7 +85,7 @@ impl FormatNodeRule for FormatExprLambda { if f.context().is_stable() || has_own_parentheses(body, f.context()).is_some() { body.format().fmt(f) } else { - maybe_parenthesize_expression(body, item, Parenthesize::IfBreaksOrIfRequired).fmt(f) + maybe_parenthesize_expression(body, item, Parenthesize::IfBreaksParenthesized).fmt(f) } } } From 80d1fd4bf3931179baf1f17ae0ea7bd7a3918704 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 11 Nov 2025 10:54:07 -0500 Subject: [PATCH 05/13] update snapshots --- ...bility@cases__preview_long_strings.py.snap | 28 +-- .../format@expression__lambda.py.snap | 191 ++++++++++-------- 2 files changed, 121 insertions(+), 98 deletions(-) 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..c19192e349 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 @@ -436,10 +436,6 @@ x = { - "A long and ridiculous {}".format(string_key): ( - "This is a really really really long string that has to go i,side of a" - " dictionary. It is soooo bad." -- ), -- some_func("calling", "some", "stuff"): ( -- "This is a really really really long string that has to go inside of a" -- " dictionary. It is {soooo} bad (#{x}).".format(sooo="soooo", x=2) + "A long and ridiculous {}".format( + string_key + ): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", @@ -448,6 +444,10 @@ x = { + ): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format( + sooo="soooo", x=2 ), +- some_func("calling", "some", "stuff"): ( +- "This is a really really really long string that has to go inside of a" +- " dictionary. It is {soooo} bad (#{x}).".format(sooo="soooo", x=2) +- ), "A %s %s" - % ("formatted", "string"): ( - "This is a really really really long string that has to go inside of a" @@ -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,119 +358,85 @@ " of it." ) @@ -914,7 +914,13 @@ x = { ) dict_with_lambda_values = { -@@ -522,65 +387,58 @@ +- "join": lambda j: ( +- f"{j.__class__.__name__}({some_function_call(j.left)}, " +- f"{some_function_call(j.right)})" +- ), ++ "join": lambda j: f"{j.__class__.__name__}({some_function_call(j.left)}, " ++ f"{some_function_call(j.right)})", + } # Complex string concatenations with a method call in the middle. code = ( @@ -998,7 +1004,7 @@ x = { ) log.info( -@@ -588,7 +446,7 @@ +@@ -588,7 +444,7 @@ ) log.info( @@ -1007,7 +1013,7 @@ x = { ) x = { -@@ -597,10 +455,10 @@ +@@ -597,10 +453,10 @@ ) } x = { @@ -1408,10 +1414,8 @@ msg = ( ) dict_with_lambda_values = { - "join": lambda j: ( - f"{j.__class__.__name__}({some_function_call(j.left)}, " - f"{some_function_call(j.right)})" - ), + "join": lambda j: f"{j.__class__.__name__}({some_function_call(j.left)}, " + f"{some_function_call(j.right)})", } # Complex string concatenations with a method call in the middle. 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 6e40c8072b..651494f34d 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 @@ -342,15 +341,20 @@ def a(): ## Outputs ### Output 1 ``` -indent-style = space -line-width = 88 -indent-width = 4 -quote-style = Double -magic-trailing-comma = Respect -preview = Disabled +indent-style = space +line-width = 88 +indent-width = 4 +quote-style = Double +line-ending = LineFeed +magic-trailing-comma = Respect +docstring-code = Disabled +docstring-code-line-width = "dynamic" +preview = Disabled +target_version = 3.10 +source_type = Python ``` -```py +```python # Leading lambda x: x # Trailing # Trailing @@ -707,15 +711,20 @@ def a(): ### Output 2 ``` -indent-style = space -line-width = 88 -indent-width = 4 -quote-style = Double -magic-trailing-comma = Respect -preview = Enabled +indent-style = space +line-width = 88 +indent-width = 4 +quote-style = Double +line-ending = LineFeed +magic-trailing-comma = Respect +docstring-code = Disabled +docstring-code-line-width = "dynamic" +preview = Enabled +target_version = 3.10 +source_type = Python ``` -```py +```python # Leading lambda x: x # Trailing # Trailing @@ -745,10 +754,30 @@ 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 @@ -774,9 +803,7 @@ 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 @@ -950,74 +977,66 @@ lambda: ( # comment # 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( From 06723e9a28db846db5a47a20fcbb80aa47bbbfff Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 11 Nov 2025 13:44:07 -0500 Subject: [PATCH 06/13] fix Parenthesize variant? --- .../src/expression/expr_lambda.rs | 3 +- ...bility@cases__preview_long_strings.py.snap | 36 +- .../format@expression__lambda.py.snap | 336 +++++++++++++----- 3 files changed, 275 insertions(+), 100 deletions(-) diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index 658bb19ef1..77e1790c4c 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -85,7 +85,8 @@ impl FormatNodeRule for FormatExprLambda { if f.context().is_stable() || has_own_parentheses(body, f.context()).is_some() { body.format().fmt(f) } else { - maybe_parenthesize_expression(body, item, Parenthesize::IfBreaksParenthesized).fmt(f) + maybe_parenthesize_expression(body, item, Parenthesize::IfBreaksParenthesizedNested) + .fmt(f) } } } 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 c19192e349..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 @@ -436,6 +436,10 @@ x = { - "A long and ridiculous {}".format(string_key): ( - "This is a really really really long string that has to go i,side of a" - " dictionary. It is soooo bad." +- ), +- some_func("calling", "some", "stuff"): ( +- "This is a really really really long string that has to go inside of a" +- " dictionary. It is {soooo} bad (#{x}).".format(sooo="soooo", x=2) + "A long and ridiculous {}".format( + string_key + ): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", @@ -444,10 +448,6 @@ x = { + ): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format( + sooo="soooo", x=2 ), -- some_func("calling", "some", "stuff"): ( -- "This is a really really really long string that has to go inside of a" -- " dictionary. It is {soooo} bad (#{x}).".format(sooo="soooo", x=2) -- ), "A %s %s" - % ("formatted", "string"): ( - "This is a really really really long string that has to go inside of a" @@ -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,119 +358,85 @@ +@@ -468,49 +358,26 @@ " of it." ) @@ -910,17 +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 = { -- "join": lambda j: ( -- f"{j.__class__.__name__}({some_function_call(j.left)}, " -- f"{some_function_call(j.right)})" -- ), -+ "join": lambda j: f"{j.__class__.__name__}({some_function_call(j.left)}, " -+ f"{some_function_call(j.right)})", - } +@@ -522,65 +389,58 @@ # Complex string concatenations with a method call in the middle. code = ( @@ -1004,7 +1000,7 @@ x = { ) log.info( -@@ -588,7 +444,7 @@ +@@ -588,7 +448,7 @@ ) log.info( @@ -1013,7 +1009,7 @@ x = { ) x = { -@@ -597,10 +453,10 @@ +@@ -597,10 +457,10 @@ ) } x = { @@ -1410,12 +1406,16 @@ 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 = { - "join": lambda j: f"{j.__class__.__name__}({some_function_call(j.left)}, " - f"{some_function_call(j.right)})", + "join": lambda j: ( + f"{j.__class__.__name__}({some_function_call(j.left)}, " + f"{some_function_call(j.right)})" + ), } # Complex string concatenations with a method call in the middle. 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 651494f34d..4d5291b6b7 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 @@ -709,6 +709,190 @@ def a(): ``` +#### 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 + +@@ -76,7 +56,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 + +@@ -250,66 +232,74 @@ + + # 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( +``` + + ### Output 2 ``` indent-style = space @@ -754,30 +938,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 @@ -803,7 +967,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 @@ -977,66 +1143,74 @@ lambda: ( # comment # 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( From 087387d40cda1b630b9220b210258a2bae28cf22 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 11 Nov 2025 14:10:00 -0500 Subject: [PATCH 07/13] preview-gate both changes --- crates/ruff_python_formatter/src/context.rs | 4 - .../src/expression/expr_lambda.rs | 89 ++++-- crates/ruff_python_formatter/src/preview.rs | 13 + .../format@expression__lambda.py.snap | 282 ++++++++++++++---- 4 files changed, 292 insertions(+), 96 deletions(-) diff --git a/crates/ruff_python_formatter/src/context.rs b/crates/ruff_python_formatter/src/context.rs index 483f41fece..528afc6c71 100644 --- a/crates/ruff_python_formatter/src/context.rs +++ b/crates/ruff_python_formatter/src/context.rs @@ -112,10 +112,6 @@ impl<'a> PyFormatContext<'a> { pub(crate) const fn is_preview(&self) -> bool { self.options.preview().is_enabled() } - - pub(crate) const fn is_stable(&self) -> bool { - !self.is_preview() - } } impl FormatContext for PyFormatContext<'_> { diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index 77e1790c4c..81ca5be61e 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -8,6 +8,8 @@ use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Pare use crate::expression::{has_own_parentheses, maybe_parenthesize_expression}; use crate::other::parameters::ParametersParentheses; use crate::prelude::*; +use crate::preview::is_indent_lambda_parameters_enabled; +use crate::preview::is_parenthesize_lambda_bodies_enabled; #[derive(Default)] pub struct FormatExprLambda; @@ -32,44 +34,67 @@ impl FormatNodeRule for FormatExprLambda { let (dangling_before_parameters, dangling_after_parameters) = dangling .split_at(dangling.partition_point(|comment| comment.end() < parameters.start())); - if dangling_before_parameters.is_empty() { - write!(f, [space()])?; - } + if is_indent_lambda_parameters_enabled(f.context()) { + if dangling_before_parameters.is_empty() { + write!(f, [space()])?; + } - group(&format_with(|f: &mut PyFormatter| { - if f.context().node_level().is_parenthesized() - && (parameters.len() > 1 || !dangling_before_parameters.is_empty()) - { - let end_of_line_start = dangling_before_parameters - .partition_point(|comment| comment.line_position().is_end_of_line()); - let (same_line_comments, own_line_comments) = - dangling_before_parameters.split_at(end_of_line_start); + group(&format_with(|f: &mut PyFormatter| { + if f.context().node_level().is_parenthesized() + && (parameters.len() > 1 || !dangling_before_parameters.is_empty()) + { + let end_of_line_start = dangling_before_parameters + .partition_point(|comment| comment.line_position().is_end_of_line()); + let (same_line_comments, own_line_comments) = + dangling_before_parameters.split_at(end_of_line_start); - dangling_comments(same_line_comments).fmt(f)?; + dangling_comments(same_line_comments).fmt(f)?; - soft_block_indent(&format_args![ - leading_comments(own_line_comments), + soft_block_indent(&format_args![ + leading_comments(own_line_comments), + parameters + .format() + .with_options(ParametersParentheses::Never), + ]) + .fmt(f) + } else { parameters .format() - .with_options(ParametersParentheses::Never), - ]) - .fmt(f) - } else { - parameters - .format() - .with_options(ParametersParentheses::Never) - .fmt(f) - }?; + .with_options(ParametersParentheses::Never) + .fmt(f) + }?; - token(":").fmt(f)?; + token(":").fmt(f)?; + + if dangling_after_parameters.is_empty() { + space().fmt(f) + } else { + dangling_comments(dangling_after_parameters).fmt(f) + } + })) + .fmt(f)?; + } else { + if dangling_before_parameters.is_empty() { + write!(f, [space()])?; + } else { + write!(f, [dangling_comments(dangling_before_parameters)])?; + } + + write!( + f, + [parameters + .format() + .with_options(ParametersParentheses::Never)] + )?; + + write!(f, [token(":")])?; if dangling_after_parameters.is_empty() { - space().fmt(f) + write!(f, [space()])?; } else { - dangling_comments(dangling_after_parameters).fmt(f) + write!(f, [dangling_comments(dangling_after_parameters)])?; } - })) - .fmt(f)?; + } } else { write!(f, [token(":")])?; @@ -82,11 +107,13 @@ impl FormatNodeRule for FormatExprLambda { } // Avoid parenthesizing lists, dictionaries, etc. - if f.context().is_stable() || has_own_parentheses(body, f.context()).is_some() { - body.format().fmt(f) - } else { + 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/preview.rs b/crates/ruff_python_formatter/src/preview.rs index 9d307390d6..4aa9eb9ab4 100644 --- a/crates/ruff_python_formatter/src/preview.rs +++ b/crates/ruff_python_formatter/src/preview.rs @@ -52,3 +52,16 @@ pub(crate) const fn is_avoid_parens_for_long_as_captures_enabled( ) -> bool { context.is_preview() } + +/// Returns `true` if the [`indent_lambda_parameters`](https://github.com/astral-sh/ruff/pull/21385) +/// preview style is enabled. +pub(crate) const fn is_indent_lambda_parameters_enabled(context: &PyFormatContext) -> 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() +} 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 4d5291b6b7..66f8eb1a40 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 @@ -417,10 +417,8 @@ a = ( ) a = ( - lambda - x, # Dangling - y - : 1 + lambda x, # Dangling + y: 1 ) # Regression test: lambda empty arguments ranges were too long, leading to unstable @@ -481,54 +479,48 @@ lambda a, /, c: a ( lambda - # comment - *x - : x + # comment + *x: x ) ( lambda - # comment - *x, - **y - : x + # comment + *x, + **y: x ) ( lambda - # comment 1 - # comment 2 - *x - : + # comment 1 + # comment 2 + *x: # comment 3 x ) ( lambda - # comment 1 - # comment 2 - *x, - **y - : + # comment 1 + # comment 2 + *x, + **y: # comment 3 x ) ( lambda # comment 1 - # comment 2 - *x - : # comment 3 + # comment 2 + *x: # comment 3 x ) ( lambda # comment 1 - # comment 2 - *x, - y - : # comment 3 + # comment 2 + *x, + y: # comment 3 x ) @@ -536,9 +528,8 @@ lambda *x: x ( lambda - # comment - *x - : x + # comment + *x: x ) lambda: ( # comment @@ -576,9 +567,8 @@ lambda: ( # comment ( lambda # 1 - # 2 - x - : # 3 + # 2 + x: # 3 # 4 # 5 # 6 @@ -587,21 +577,18 @@ lambda: ( # comment ( lambda # 1 - # 2 - x, # 3 - # 4 - y - : # 5 + # 2 + x, # 3 + # 4 + y: # 5 # 6 x ) ( - lambda - x, - # comment - y - : z + lambda x, + # comment + y: z ) @@ -680,11 +667,9 @@ def a(): c, d, e, - f=lambda - self, - *args, - **kwargs - : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), + f=lambda self, + *args, + **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), ) @@ -693,17 +678,15 @@ def a(): 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, ) ``` @@ -748,7 +731,20 @@ def a(): ) # Trailing # Trailing -@@ -76,7 +56,9 @@ +@@ -60,8 +40,10 @@ + ) + + a = ( +- lambda x, # Dangling +- y: 1 ++ lambda ++ x, # Dangling ++ y ++ : 1 + ) + + # Regression test: lambda empty arguments ranges were too long, leading to unstable +@@ -74,7 +56,9 @@ # lambda arguments don't have parentheses, so we never add a magic trailing comma ... def f( @@ -759,7 +755,130 @@ def a(): ): pass -@@ -250,66 +232,74 @@ +@@ -122,48 +106,54 @@ + + ( + lambda +- # comment +- *x: x ++ # comment ++ *x ++ : x + ) + + ( + lambda +- # comment +- *x, +- **y: x ++ # comment ++ *x, ++ **y ++ : x + ) + + ( + lambda +- # comment 1 +- # comment 2 +- *x: ++ # comment 1 ++ # comment 2 ++ *x ++ : + # comment 3 + x + ) + + ( + lambda +- # comment 1 +- # comment 2 +- *x, +- **y: ++ # comment 1 ++ # comment 2 ++ *x, ++ **y ++ : + # comment 3 + x + ) + + ( + lambda # comment 1 +- # comment 2 +- *x: # comment 3 ++ # comment 2 ++ *x ++ : # comment 3 + x + ) + + ( + lambda # comment 1 +- # comment 2 +- *x, +- y: # comment 3 ++ # comment 2 ++ *x, ++ y ++ : # comment 3 + x + ) + +@@ -171,8 +161,9 @@ + + ( + lambda +- # comment +- *x: x ++ # comment ++ *x ++ : x + ) + + lambda: ( # comment +@@ -210,8 +201,9 @@ + + ( + lambda # 1 +- # 2 +- x: # 3 ++ # 2 ++ x ++ : # 3 + # 4 + # 5 + # 6 +@@ -220,83 +212,94 @@ + + ( + lambda # 1 +- # 2 +- x, # 3 +- # 4 +- y: # 5 ++ # 2 ++ x, # 3 ++ # 4 ++ y ++ : # 5 + # 6 + x + ) + + ( +- lambda x, +- # comment +- y: z ++ lambda ++ x, ++ # comment ++ y ++ : z + ) + # Leading lambda x: ( @@ -890,6 +1009,47 @@ def a(): # Trailing lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( +@@ -310,9 +313,11 @@ + c, + d, + e, +- f=lambda self, +- *args, +- **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), ++ f=lambda ++ self, ++ *args, ++ **kwargs ++ : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), + ) + + +@@ -321,14 +326,16 @@ + 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, + ) ``` From cc84b4f0564903335f865d2e2a806ff82c74a561 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 12 Nov 2025 08:33:22 -0500 Subject: [PATCH 08/13] move unstable tests to preview file, finish preview gating --- .../test/fixtures/ruff/expression/lambda.py | 19 ---- .../expression/lambda_preview.options.json | 5 + .../ruff/expression/lambda_preview.py | 24 +++++ .../src/other/parameters.rs | 7 +- .../format@expression__lambda.py.snap | 102 ++---------------- .../format@expression__lambda_preview.py.snap | 75 +++++++++++++ 6 files changed, 118 insertions(+), 114 deletions(-) create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.options.json create mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.py create mode 100644 crates/ruff_python_formatter/tests/snapshots/format@expression__lambda_preview.py.snap 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 41007d3381..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 @@ -142,17 +142,6 @@ lambda a, /, c: a x ) -( - lambda - # comment 1 - * - # comment 2 - x, - **y: - # comment 3 - x -) - ( lambda # comment 1 * # comment 2 @@ -160,14 +149,6 @@ lambda a, /, c: a x ) -( - lambda # comment 1 - * # comment 2 - x, - y: # comment 3 - x -) - lambda *x\ :x diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.options.json b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.options.json new file mode 100644 index 0000000000..8925dd0a82 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.options.json @@ -0,0 +1,5 @@ +[ + { + "preview": "enabled" + } +] diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.py new file mode 100644 index 0000000000..819ab48b32 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.py @@ -0,0 +1,24 @@ +""" +Test cases from lambda.py that cause an instability in the stable +implementation but that are handled by the preview `indent_lambda_parameters` +version. +""" + +( + lambda + # comment 1 + * + # comment 2 + x, + **y: + # comment 3 + x +) + +( + lambda # comment 1 + * # comment 2 + x, + y: # comment 3 + x +) diff --git a/crates/ruff_python_formatter/src/other/parameters.rs b/crates/ruff_python_formatter/src/other/parameters.rs index 736d0d6fa0..377c659ecd 100644 --- a/crates/ruff_python_formatter/src/other/parameters.rs +++ b/crates/ruff_python_formatter/src/other/parameters.rs @@ -10,6 +10,7 @@ use crate::comments::{ use crate::context::{NodeLevel, WithNodeLevel}; use crate::expression::parentheses::empty_parenthesized; use crate::prelude::*; +use crate::preview::is_indent_lambda_parameters_enabled; #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] pub enum ParametersParentheses { @@ -241,7 +242,11 @@ impl FormatNodeRule for FormatParameters { let num_parameters = item.len(); if self.parentheses == ParametersParentheses::Never { - write!(f, [format_inner, dangling_comments(dangling)]) + if is_indent_lambda_parameters_enabled(f.context()) { + write!(f, [format_inner, dangling_comments(dangling)]) + } else { + write!(f, [group(&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 `()` 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 66f8eb1a40..22ed08447e 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 @@ -148,17 +148,6 @@ lambda a, /, c: a x ) -( - lambda - # comment 1 - * - # comment 2 - x, - **y: - # comment 3 - x -) - ( lambda # comment 1 * # comment 2 @@ -166,14 +155,6 @@ lambda a, /, c: a x ) -( - lambda # comment 1 - * # comment 2 - x, - y: # comment 3 - x -) - lambda *x\ :x @@ -486,8 +467,7 @@ lambda a, /, c: a ( lambda # comment - *x, - **y: x + *x, **y: x ) ( @@ -499,16 +479,6 @@ lambda a, /, c: a x ) -( - lambda - # comment 1 - # comment 2 - *x, - **y: - # comment 3 - x -) - ( lambda # comment 1 # comment 2 @@ -516,14 +486,6 @@ lambda a, /, c: a x ) -( - lambda # comment 1 - # comment 2 - *x, - y: # comment 3 - x -) - lambda *x: x ( @@ -755,7 +717,7 @@ def a(): ): pass -@@ -122,48 +106,54 @@ +@@ -122,29 +106,34 @@ ( lambda @@ -769,8 +731,7 @@ def a(): ( lambda - # comment -- *x, -- **y: x +- *x, **y: x + # comment + *x, + **y @@ -790,21 +751,6 @@ def a(): x ) - ( - lambda -- # comment 1 -- # comment 2 -- *x, -- **y: -+ # comment 1 -+ # comment 2 -+ *x, -+ **y -+ : - # comment 3 - x - ) - ( lambda # comment 1 - # comment 2 @@ -815,19 +761,7 @@ def a(): x ) - ( - lambda # comment 1 -- # comment 2 -- *x, -- y: # comment 3 -+ # comment 2 -+ *x, -+ y -+ : # comment 3 - x - ) - -@@ -171,8 +161,9 @@ +@@ -152,8 +141,9 @@ ( lambda @@ -839,7 +773,7 @@ def a(): ) lambda: ( # comment -@@ -210,8 +201,9 @@ +@@ -191,8 +181,9 @@ ( lambda # 1 @@ -851,7 +785,7 @@ def a(): # 4 # 5 # 6 -@@ -220,83 +212,94 @@ +@@ -201,83 +192,94 @@ ( lambda # 1 @@ -1009,7 +943,7 @@ def a(): # Trailing lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( -@@ -310,9 +313,11 @@ +@@ -291,9 +293,11 @@ c, d, e, @@ -1024,7 +958,7 @@ def a(): ) -@@ -321,14 +326,16 @@ +@@ -302,14 +306,16 @@ c, d, e, @@ -1200,17 +1134,6 @@ lambda a, /, c: a x ) -( - lambda - # comment 1 - # comment 2 - *x, - **y - : - # comment 3 - x -) - ( lambda # comment 1 # comment 2 @@ -1219,15 +1142,6 @@ lambda a, /, c: a x ) -( - lambda # comment 1 - # comment 2 - *x, - y - : # comment 3 - x -) - lambda *x: x ( diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda_preview.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda_preview.py.snap new file mode 100644 index 0000000000..eeb6fa2c36 --- /dev/null +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda_preview.py.snap @@ -0,0 +1,75 @@ +--- +source: crates/ruff_python_formatter/tests/fixtures.rs +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.py +--- +## Input +```python +""" +Test cases from lambda.py that cause an instability in the stable +implementation but that are handled by the preview `indent_lambda_parameters` +version. +""" + +( + lambda + # comment 1 + * + # comment 2 + x, + **y: + # comment 3 + x +) + +( + lambda # comment 1 + * # comment 2 + x, + y: # comment 3 + x +) +``` + +## Outputs +### Output 1 +``` +indent-style = space +line-width = 88 +indent-width = 4 +quote-style = Double +line-ending = LineFeed +magic-trailing-comma = Respect +docstring-code = Disabled +docstring-code-line-width = "dynamic" +preview = Enabled +target_version = 3.10 +source_type = Python +``` + +```python +""" +Test cases from lambda.py that cause an instability in the stable +implementation but that are handled by the preview `indent_lambda_parameters` +version. +""" + +( + lambda + # comment 1 + # comment 2 + *x, + **y + : + # comment 3 + x +) + +( + lambda # comment 1 + # comment 2 + *x, + y + : # comment 3 + x +) +``` From 19326a7b5e953041cee0ed184ff991b21d2df359 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 12 Nov 2025 08:37:18 -0500 Subject: [PATCH 09/13] factor out more shared code --- .../src/expression/expr_lambda.rs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index 81ca5be61e..c614f87023 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -62,14 +62,6 @@ impl FormatNodeRule for FormatExprLambda { .format() .with_options(ParametersParentheses::Never) .fmt(f) - }?; - - token(":").fmt(f)?; - - if dangling_after_parameters.is_empty() { - space().fmt(f) - } else { - dangling_comments(dangling_after_parameters).fmt(f) } })) .fmt(f)?; @@ -86,14 +78,14 @@ impl FormatNodeRule for FormatExprLambda { .format() .with_options(ParametersParentheses::Never)] )?; + } - write!(f, [token(":")])?; + write!(f, [token(":")])?; - if dangling_after_parameters.is_empty() { - write!(f, [space()])?; - } else { - write!(f, [dangling_comments(dangling_after_parameters)])?; - } + if dangling_after_parameters.is_empty() { + write!(f, [space()])?; + } else { + write!(f, [dangling_comments(dangling_after_parameters)])?; } } else { write!(f, [token(":")])?; From dfb56d39f681ae7627c64e8670f2e75a8da97c0d Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 12 Nov 2025 09:35:46 -0500 Subject: [PATCH 10/13] revert indent_lambda_parameters --- .../src/expression/expr_lambda.rs | 58 +---- .../src/other/parameters.rs | 7 +- crates/ruff_python_formatter/src/preview.rs | 6 - .../format@expression__lambda.py.snap | 237 +++--------------- 4 files changed, 49 insertions(+), 259 deletions(-) diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index c614f87023..9aec3c8825 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -1,14 +1,13 @@ -use ruff_formatter::{format_args, write}; +use ruff_formatter::write; use ruff_python_ast::AnyNodeRef; use ruff_python_ast::ExprLambda; use ruff_text_size::Ranged; -use crate::comments::{dangling_comments, leading_comments}; +use crate::comments::dangling_comments; 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_indent_lambda_parameters_enabled; use crate::preview::is_parenthesize_lambda_bodies_enabled; #[derive(Default)] @@ -34,52 +33,19 @@ impl FormatNodeRule for FormatExprLambda { let (dangling_before_parameters, dangling_after_parameters) = dangling .split_at(dangling.partition_point(|comment| comment.end() < parameters.start())); - if is_indent_lambda_parameters_enabled(f.context()) { - if dangling_before_parameters.is_empty() { - write!(f, [space()])?; - } - - group(&format_with(|f: &mut PyFormatter| { - if f.context().node_level().is_parenthesized() - && (parameters.len() > 1 || !dangling_before_parameters.is_empty()) - { - let end_of_line_start = dangling_before_parameters - .partition_point(|comment| comment.line_position().is_end_of_line()); - let (same_line_comments, own_line_comments) = - dangling_before_parameters.split_at(end_of_line_start); - - dangling_comments(same_line_comments).fmt(f)?; - - soft_block_indent(&format_args![ - leading_comments(own_line_comments), - parameters - .format() - .with_options(ParametersParentheses::Never), - ]) - .fmt(f) - } else { - parameters - .format() - .with_options(ParametersParentheses::Never) - .fmt(f) - } - })) - .fmt(f)?; + if dangling_before_parameters.is_empty() { + write!(f, [space()])?; } else { - if dangling_before_parameters.is_empty() { - write!(f, [space()])?; - } else { - write!(f, [dangling_comments(dangling_before_parameters)])?; - } - - write!( - f, - [parameters - .format() - .with_options(ParametersParentheses::Never)] - )?; + write!(f, [dangling_comments(dangling_before_parameters)])?; } + write!( + f, + [parameters + .format() + .with_options(ParametersParentheses::Never)] + )?; + write!(f, [token(":")])?; if dangling_after_parameters.is_empty() { diff --git a/crates/ruff_python_formatter/src/other/parameters.rs b/crates/ruff_python_formatter/src/other/parameters.rs index 377c659ecd..1c6682bab1 100644 --- a/crates/ruff_python_formatter/src/other/parameters.rs +++ b/crates/ruff_python_formatter/src/other/parameters.rs @@ -10,7 +10,6 @@ use crate::comments::{ use crate::context::{NodeLevel, WithNodeLevel}; use crate::expression::parentheses::empty_parenthesized; use crate::prelude::*; -use crate::preview::is_indent_lambda_parameters_enabled; #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] pub enum ParametersParentheses { @@ -242,11 +241,7 @@ impl FormatNodeRule for FormatParameters { let num_parameters = item.len(); if self.parentheses == ParametersParentheses::Never { - if is_indent_lambda_parameters_enabled(f.context()) { - write!(f, [format_inner, dangling_comments(dangling)]) - } else { - write!(f, [group(&format_inner), dangling_comments(dangling)]) - } + write!(f, [group(&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 `()` diff --git a/crates/ruff_python_formatter/src/preview.rs b/crates/ruff_python_formatter/src/preview.rs index 4aa9eb9ab4..62b6b90033 100644 --- a/crates/ruff_python_formatter/src/preview.rs +++ b/crates/ruff_python_formatter/src/preview.rs @@ -53,12 +53,6 @@ pub(crate) const fn is_avoid_parens_for_long_as_captures_enabled( context.is_preview() } -/// Returns `true` if the [`indent_lambda_parameters`](https://github.com/astral-sh/ruff/pull/21385) -/// preview style is enabled. -pub(crate) const fn is_indent_lambda_parameters_enabled(context: &PyFormatContext) -> bool { - context.is_preview() -} - /// Returns `true` if the /// [`parenthesize_lambda_bodies`](https://github.com/astral-sh/ruff/pull/21385) preview style is /// enabled. 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 22ed08447e..8a8b1bd77a 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 @@ -693,20 +693,7 @@ def a(): ) # Trailing # Trailing -@@ -60,8 +40,10 @@ - ) - - a = ( -- lambda x, # Dangling -- y: 1 -+ lambda -+ x, # Dangling -+ y -+ : 1 - ) - - # Regression test: lambda empty arguments ranges were too long, leading to unstable -@@ -74,7 +56,9 @@ +@@ -74,7 +54,9 @@ # lambda arguments don't have parentheses, so we never add a magic trailing comma ... def f( @@ -717,102 +704,7 @@ def a(): ): pass -@@ -122,29 +106,34 @@ - - ( - lambda -- # comment -- *x: x -+ # comment -+ *x -+ : x - ) - - ( - lambda -- # comment -- *x, **y: x -+ # comment -+ *x, -+ **y -+ : x - ) - - ( - lambda -- # comment 1 -- # comment 2 -- *x: -+ # comment 1 -+ # comment 2 -+ *x -+ : - # comment 3 - x - ) - - ( - lambda # comment 1 -- # comment 2 -- *x: # comment 3 -+ # comment 2 -+ *x -+ : # comment 3 - x - ) - -@@ -152,8 +141,9 @@ - - ( - lambda -- # comment -- *x: x -+ # comment -+ *x -+ : x - ) - - lambda: ( # comment -@@ -191,8 +181,9 @@ - - ( - lambda # 1 -- # 2 -- x: # 3 -+ # 2 -+ x -+ : # 3 - # 4 - # 5 - # 6 -@@ -201,83 +192,94 @@ - - ( - lambda # 1 -- # 2 -- x, # 3 -- # 4 -- y: # 5 -+ # 2 -+ x, # 3 -+ # 4 -+ y -+ : # 5 - # 6 - x - ) - - ( -- lambda x, -- # comment -- y: z -+ lambda -+ x, -+ # comment -+ y -+ : z - ) - +@@ -218,66 +200,74 @@ # Leading lambda x: ( @@ -943,47 +835,6 @@ def a(): # Trailing lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( -@@ -291,9 +293,11 @@ - c, - d, - e, -- f=lambda self, -- *args, -- **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), -+ f=lambda -+ self, -+ *args, -+ **kwargs -+ : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), - ) - - -@@ -302,14 +306,16 @@ - 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, - ) ``` @@ -1045,10 +896,8 @@ a = ( ) a = ( - lambda - x, # Dangling - y - : 1 + lambda x, # Dangling + y: 1 ) # Regression test: lambda empty arguments ranges were too long, leading to unstable @@ -1111,34 +960,29 @@ lambda a, /, c: a ( lambda - # comment - *x - : x + # comment + *x: x ) ( lambda - # comment - *x, - **y - : x + # comment + *x, **y: x ) ( lambda - # comment 1 - # comment 2 - *x - : + # comment 1 + # comment 2 + *x: # comment 3 x ) ( lambda # comment 1 - # comment 2 - *x - : # comment 3 + # comment 2 + *x: # comment 3 x ) @@ -1146,9 +990,8 @@ lambda *x: x ( lambda - # comment - *x - : x + # comment + *x: x ) lambda: ( # comment @@ -1186,9 +1029,8 @@ lambda: ( # comment ( lambda # 1 - # 2 - x - : # 3 + # 2 + x: # 3 # 4 # 5 # 6 @@ -1197,21 +1039,18 @@ lambda: ( # comment ( lambda # 1 - # 2 - x, # 3 - # 4 - y - : # 5 + # 2 + x, # 3 + # 4 + y: # 5 # 6 x ) ( - lambda - x, - # comment - y - : z + lambda x, + # comment + y: z ) @@ -1298,11 +1137,9 @@ def a(): c, d, e, - f=lambda - self, - *args, - **kwargs - : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), + f=lambda self, + *args, + **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), ) @@ -1311,17 +1148,15 @@ def a(): 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, ) ``` From 8fa21ab5fa7a7b9ff75688027beafffe69003af3 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 12 Nov 2025 09:42:25 -0500 Subject: [PATCH 11/13] delete unstable lambda tests --- .../expression/lambda_preview.options.json | 5 -- .../ruff/expression/lambda_preview.py | 24 ------ .../format@expression__lambda_preview.py.snap | 75 ------------------- 3 files changed, 104 deletions(-) delete mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.options.json delete mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.py delete mode 100644 crates/ruff_python_formatter/tests/snapshots/format@expression__lambda_preview.py.snap diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.options.json b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.options.json deleted file mode 100644 index 8925dd0a82..0000000000 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.options.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - { - "preview": "enabled" - } -] diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.py deleted file mode 100644 index 819ab48b32..0000000000 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Test cases from lambda.py that cause an instability in the stable -implementation but that are handled by the preview `indent_lambda_parameters` -version. -""" - -( - lambda - # comment 1 - * - # comment 2 - x, - **y: - # comment 3 - x -) - -( - lambda # comment 1 - * # comment 2 - x, - y: # comment 3 - x -) diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda_preview.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda_preview.py.snap deleted file mode 100644 index eeb6fa2c36..0000000000 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda_preview.py.snap +++ /dev/null @@ -1,75 +0,0 @@ ---- -source: crates/ruff_python_formatter/tests/fixtures.rs -input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda_preview.py ---- -## Input -```python -""" -Test cases from lambda.py that cause an instability in the stable -implementation but that are handled by the preview `indent_lambda_parameters` -version. -""" - -( - lambda - # comment 1 - * - # comment 2 - x, - **y: - # comment 3 - x -) - -( - lambda # comment 1 - * # comment 2 - x, - y: # comment 3 - x -) -``` - -## Outputs -### Output 1 -``` -indent-style = space -line-width = 88 -indent-width = 4 -quote-style = Double -line-ending = LineFeed -magic-trailing-comma = Respect -docstring-code = Disabled -docstring-code-line-width = "dynamic" -preview = Enabled -target_version = 3.10 -source_type = Python -``` - -```python -""" -Test cases from lambda.py that cause an instability in the stable -implementation but that are handled by the preview `indent_lambda_parameters` -version. -""" - -( - lambda - # comment 1 - # comment 2 - *x, - **y - : - # comment 3 - x -) - -( - lambda # comment 1 - # comment 2 - *x, - y - : # comment 3 - x -) -``` From 7bcfec1156c36c7cda36ce6ac9ba11cd0a825ddb Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 12 Nov 2025 09:59:32 -0500 Subject: [PATCH 12/13] remove lambda.options.json, redundant with preview changes section --- .../ruff/expression/lambda.options.json | 8 - .../format@expression__lambda.py.snap | 343 +----------------- 2 files changed, 2 insertions(+), 349 deletions(-) delete mode 100644 crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.options.json diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.options.json b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.options.json deleted file mode 100644 index 92e14f0a45..0000000000 --- a/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/lambda.options.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "preview": "disabled" - }, - { - "preview": "enabled" - } -] 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 8a8b1bd77a..5e0240b64f 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 @@ -319,22 +319,7 @@ def a(): ``` -## Outputs -### Output 1 -``` -indent-style = space -line-width = 88 -indent-width = 4 -quote-style = Double -line-ending = LineFeed -magic-trailing-comma = Respect -docstring-code = Disabled -docstring-code-line-width = "dynamic" -preview = Disabled -target_version = 3.10 -source_type = Python -``` - +## Output ```python # Leading lambda x: x # Trailing @@ -654,7 +639,7 @@ def a(): ``` -#### Preview changes +## Preview changes ```diff --- Stable +++ Preview @@ -836,327 +821,3 @@ def a(): lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( ``` - - -### Output 2 -``` -indent-style = space -line-width = 88 -indent-width = 4 -quote-style = Double -line-ending = LineFeed -magic-trailing-comma = Respect -docstring-code = Disabled -docstring-code-line-width = "dynamic" -preview = Enabled -target_version = 3.10 -source_type = Python -``` - -```python -# Leading -lambda x: x # Trailing -# Trailing - -# Leading -lambda x, y: x # Trailing -# Trailing - -# Leading -lambda x, y: x, y # Trailing -# Trailing - -# Leading -lambda x, /, y: x # Trailing -# Trailing - -# Leading -lambda x: lambda y: lambda z: x # Trailing -# Trailing - -# Leading -lambda x: lambda y: lambda z: (x, y, z) # Trailing -# Trailing - -# Leading -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) - ) -) # Trailing -# Trailing - -a = ( - lambda: # Dangling - 1 -) - -a = ( - lambda x, # Dangling - y: 1 -) - -# Regression test: lambda empty arguments ranges were too long, leading to unstable -# formatting -( - lambda: ( # - ), -) - - -# lambda arguments don't have parentheses, so we never add a magic trailing comma ... -def f( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = lambda x: ( - y - ), -): - pass - - -# ...but we do preserve a trailing comma after the arguments -a = lambda b,: 0 - -lambda a,: 0 -lambda *args,: 0 -lambda **kwds,: 0 -lambda a, *args,: 0 -lambda a, **kwds,: 0 -lambda *args, b,: 0 -lambda *, b,: 0 -lambda *args, **kwds,: 0 -lambda a, *args, b,: 0 -lambda a, *, b,: 0 -lambda a, *args, **kwds,: 0 -lambda *args, b, **kwds,: 0 -lambda *, b, **kwds,: 0 -lambda a, *args, b, **kwds,: 0 -lambda a, *, b, **kwds,: 0 -lambda a, /: a -lambda a, /, c: a - -# Dangling comments without parameters. -( - lambda: # 3 - None -) - -( - lambda: - # 3 - None -) - -( - lambda: # 1 - # 2 - # 3 - # 4 - None # 5 -) - -( - lambda - # comment - *x: x -) - -( - lambda - # comment - *x, **y: x -) - -( - lambda - # comment 1 - # comment 2 - *x: - # comment 3 - x -) - -( - lambda # comment 1 - # comment 2 - *x: # comment 3 - x -) - -lambda *x: x - -( - lambda - # comment - *x: x -) - -lambda: ( # comment - x -) - -( - lambda: # comment - x -) - -( - lambda: - # comment - x -) - -( - lambda: # comment - x -) - -( - lambda: - # comment - x -) - -( - lambda: # comment - ( # comment - x - ) -) - -( - lambda # 1 - # 2 - x: # 3 - # 4 - # 5 - # 6 - 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 - - -# Regression tests for https://github.com/astral-sh/ruff/issues/8179 -def a(): - return b( - c, - d, - e, - f=lambda self, - *args, - **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), - ) - - -def a(): - return b( - c, - d, - e, - f=lambda self, - araa, - kkkwargs, - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, - args, - kwargs, - e=1, - f=2, - g=2: d, - g=10, - ) -``` From 32d7d52d2af7591cf0d03c01aee319693b54cfb3 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 12 Nov 2025 10:28:11 -0500 Subject: [PATCH 13/13] try RemoveSoftLinesBuffer --- .../src/expression/expr_lambda.rs | 27 +++++++++--- crates/ruff_python_formatter/src/preview.rs | 9 ++++ .../format@expression__lambda.py.snap | 43 ++++++++++++++++++- 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index 9aec3c8825..bec702ea52 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -1,3 +1,4 @@ +use ruff_formatter::RemoveSoftLinesBuffer; use ruff_formatter::write; use ruff_python_ast::AnyNodeRef; use ruff_python_ast::ExprLambda; @@ -8,6 +9,7 @@ use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Pare 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)] @@ -39,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(":")])?; diff --git a/crates/ruff_python_formatter/src/preview.rs b/crates/ruff_python_formatter/src/preview.rs index 62b6b90033..fe55eef9a0 100644 --- a/crates/ruff_python_formatter/src/preview.rs +++ b/crates/ruff_python_formatter/src/preview.rs @@ -59,3 +59,12 @@ pub(crate) const fn is_avoid_parens_for_long_as_captures_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/format@expression__lambda.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__lambda.py.snap index 5e0240b64f..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 @@ -689,7 +689,7 @@ def a(): ): pass -@@ -218,66 +200,74 @@ +@@ -218,71 +200,79 @@ # Leading lambda x: ( @@ -819,5 +819,44 @@ def a(): +) # Trailing # Trailing - lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( +-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, + ) ```