diff --git a/crates/ruff_python_formatter/src/expression/expr_lambda.rs b/crates/ruff_python_formatter/src/expression/expr_lambda.rs index a4f391d8b3..c4f67d4690 100644 --- a/crates/ruff_python_formatter/src/expression/expr_lambda.rs +++ b/crates/ruff_python_formatter/src/expression/expr_lambda.rs @@ -137,12 +137,16 @@ impl FormatNodeRule for FormatExprLambda { write!(f, [dangling_comments(dangling)])?; } - FormatBody { - body, - dangling, - layout: self.layout, + if !preview { + return body.format().fmt(f); + } + + let fmt_body = FormatBody { body, dangling }; + + match self.layout { + ExprLambdaLayout::Assignment => fits_expanded(&fmt_body).fmt(f), + ExprLambdaLayout::Default => fmt_body.fmt(f), } - .fmt(f) } } @@ -203,219 +207,203 @@ impl NeedsParentheses for ExprLambda { struct FormatBody<'a> { body: &'a Expr, dangling: &'a [SourceComment], - layout: ExprLambdaLayout, } impl Format> for FormatBody<'_> { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { - let FormatBody { - dangling, - body, - layout, - } = self; - - if !is_parenthesize_lambda_bodies_enabled(f.context()) { - return body.format().fmt(f); - } + let FormatBody { dangling, body } = self; let body = *body; let comments = f.context().comments().clone(); let body_comments = comments.leading_dangling_trailing(body); - let fmt_body = format_with(|f: &mut PyFormatter| { - if !dangling.is_empty() { - // Can't use partition_point because there can be additional end of line comments - // after the initial set. All of these comments are dangling, for example: - // - // ```python - // ( - // lambda # 1 - // # 2 - // : # 3 - // # 4 - // y - // ) - // ``` - // - // and alternate between own line and end of line. - let (after_parameters_end_of_line, leading_body_comments) = dangling.split_at( - dangling - .iter() - .position(|comment| comment.line_position().is_own_line()) - .unwrap_or(dangling.len()), - ); + if !dangling.is_empty() { + // Can't use partition_point because there can be additional end of line comments + // after the initial set. All of these comments are dangling, for example: + // + // ```python + // ( + // lambda # 1 + // # 2 + // : # 3 + // # 4 + // y + // ) + // ``` + // + // and alternate between own line and end of line. + let (after_parameters_end_of_line, leading_body_comments) = dangling.split_at( + dangling + .iter() + .position(|comment| comment.line_position().is_own_line()) + .unwrap_or(dangling.len()), + ); - // If the body is parenthesized and has its own leading comments, preserve the - // separation between the dangling lambda comments and the body comments. For - // example, preserve this comment positioning: - // - // ```python - // ( - // lambda: # 1 - // # 2 - // ( # 3 - // x - // ) - // ) - // ``` - // - // 1 and 2 are dangling on the lambda and emitted first, followed by a hard line - // break and the parenthesized body with its leading comments. - // - // However, when removing 2, 1 and 3 can instead be formatted on the same line: - // - // ```python - // ( - // lambda: ( # 1 # 3 - // x - // ) - // ) - // ``` - let comments = f.context().comments(); - if is_expression_parenthesized(body.into(), comments.ranges(), f.context().source()) - && comments.has_leading(body) - { - trailing_comments(dangling).fmt(f)?; + // If the body is parenthesized and has its own leading comments, preserve the + // separation between the dangling lambda comments and the body comments. For + // example, preserve this comment positioning: + // + // ```python + // ( + // lambda: # 1 + // # 2 + // ( # 3 + // x + // ) + // ) + // ``` + // + // 1 and 2 are dangling on the lambda and emitted first, followed by a hard line + // break and the parenthesized body with its leading comments. + // + // However, when removing 2, 1 and 3 can instead be formatted on the same line: + // + // ```python + // ( + // lambda: ( # 1 # 3 + // x + // ) + // ) + // ``` + let comments = f.context().comments(); + if is_expression_parenthesized(body.into(), comments.ranges(), f.context().source()) + && comments.has_leading(body) + { + trailing_comments(dangling).fmt(f)?; - if leading_body_comments.is_empty() { - space().fmt(f)?; - } else { - hard_line_break().fmt(f)?; - } - - body.format().with_options(Parentheses::Always).fmt(f) + if leading_body_comments.is_empty() { + space().fmt(f)?; } else { - write!( - f, - [ - space(), - token("("), - trailing_comments(after_parameters_end_of_line), - block_indent(&format_args!( - leading_comments(leading_body_comments), - body.format().with_options(Parentheses::Never) - )), - token(")") - ] - ) + hard_line_break().fmt(f)?; } - } - // If the body has comments, we always want to preserve the parentheses. This also - // ensures that we correctly handle parenthesized comments, and don't need to worry - // about them in the implementation below. - else if body_comments.has_leading() || body_comments.has_trailing_own_line() { + body.format().with_options(Parentheses::Always).fmt(f) - } - // Calls and subscripts require special formatting because they have their own - // parentheses, but they can also have an arbitrary amount of text before the - // opening parenthesis. We want to avoid cases where we keep a long callable on the - // same line as the lambda parameters. For example, `db_evmtx...` in: - // - // ```py - // transaction_count = self._query_txs_for_range( - // get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range( - // chain_id=_chain_id, - // from_ts=from_ts, - // to_ts=to_ts, - // ), - // ) - // ``` - // - // should cause the whole lambda body to be parenthesized instead: - // - // ```py - // transaction_count = self._query_txs_for_range( - // get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: ( - // db_evmtx.count_transactions_in_range( - // chain_id=_chain_id, - // from_ts=from_ts, - // to_ts=to_ts, - // ) - // ), - // ) - // ``` - else if matches!(body, Expr::Call(_) | Expr::Subscript(_)) { - let unparenthesized = body.format().with_options(Parentheses::Never); - if CallChainLayout::from_expression( - body.into(), - comments.ranges(), - f.context().source(), - ) == CallChainLayout::Fluent - { - parenthesize_if_expands(&unparenthesized).fmt(f) - } else { - let unparenthesized = unparenthesized.memoized(); - if unparenthesized.inspect(f)?.will_break() { - expand_parent().fmt(f)?; - } - - best_fitting![ - // body all flat - unparenthesized, - // body expanded - group(&unparenthesized).should_expand(true), - // parenthesized - format_args![token("("), block_indent(&unparenthesized), token(")")] + } else { + write!( + f, + [ + space(), + token("("), + trailing_comments(after_parameters_end_of_line), + block_indent(&format_args!( + leading_comments(leading_body_comments), + body.format().with_options(Parentheses::Never) + )), + token(")") ] - .fmt(f) + ) + } + } + // If the body has comments, we always want to preserve the parentheses. This also + // ensures that we correctly handle parenthesized comments, and don't need to worry + // about them in the implementation below. + else if body_comments.has_leading() || body_comments.has_trailing_own_line() { + body.format().with_options(Parentheses::Always).fmt(f) + } + // Calls and subscripts require special formatting because they have their own + // parentheses, but they can also have an arbitrary amount of text before the + // opening parenthesis. We want to avoid cases where we keep a long callable on the + // same line as the lambda parameters. For example, `db_evmtx...` in: + // + // ```py + // transaction_count = self._query_txs_for_range( + // get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range( + // chain_id=_chain_id, + // from_ts=from_ts, + // to_ts=to_ts, + // ), + // ) + // ``` + // + // should cause the whole lambda body to be parenthesized instead: + // + // ```py + // transaction_count = self._query_txs_for_range( + // get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: ( + // db_evmtx.count_transactions_in_range( + // chain_id=_chain_id, + // from_ts=from_ts, + // to_ts=to_ts, + // ) + // ), + // ) + // ``` + else if matches!(body, Expr::Call(_) | Expr::Subscript(_)) { + let unparenthesized = body.format().with_options(Parentheses::Never); + if CallChainLayout::from_expression( + body.into(), + comments.ranges(), + f.context().source(), + ) == CallChainLayout::Fluent + { + parenthesize_if_expands(&unparenthesized).fmt(f) + } else { + let unparenthesized = unparenthesized.memoized(); + if unparenthesized.inspect(f)?.will_break() { + expand_parent().fmt(f)?; } - } - // For other cases with their own parentheses, such as lists, sets, dicts, tuples, - // etc., we can just format the body directly. Their own formatting results in the - // lambda being formatted well too. For example: - // - // ```py - // lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: [xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz] - // ``` - // - // gets formatted as: - // - // ```py - // lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: [ - // xxxxxxxxxxxxxxxxxxxx, - // yyyyyyyyyyyyyyyyyyyy, - // zzzzzzzzzzzzzzzzzzzz - // ] - // ``` - else if has_own_parentheses(body, f.context()).is_some() { - body.format().fmt(f) - } - // Finally, for expressions without their own parentheses, use - // `parenthesize_if_expands` to add parentheses around the body, only if it expands - // across multiple lines. The `Parentheses::Never` here also removes unnecessary - // parentheses around lambda bodies that fit on one line. For example: - // - // ```py - // lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: xxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyy + zzzzzzzzzzzzzzzzzzzz - // ``` - // - // is formatted as: - // - // ```py - // lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: ( - // xxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyy + zzzzzzzzzzzzzzzzzzzz - // ) - // ``` - // - // while - // - // ```py - // lambda xxxxxxxxxxxxxxxxxxxx: (xxxxxxxxxxxxxxxxxxxx + 1) - // ``` - // - // is formatted as: - // - // ```py - // lambda xxxxxxxxxxxxxxxxxxxx: xxxxxxxxxxxxxxxxxxxx + 1 - // ``` - else { - parenthesize_if_expands(&body.format().with_options(Parentheses::Never)).fmt(f) - } - }); - match layout { - ExprLambdaLayout::Assignment => fits_expanded(&fmt_body).fmt(f), - ExprLambdaLayout::Default => fmt_body.fmt(f), + best_fitting![ + // body all flat + unparenthesized, + // body expanded + group(&unparenthesized).should_expand(true), + // parenthesized + format_args![token("("), block_indent(&unparenthesized), token(")")] + ] + .fmt(f) + } + } + // For other cases with their own parentheses, such as lists, sets, dicts, tuples, + // etc., we can just format the body directly. Their own formatting results in the + // lambda being formatted well too. For example: + // + // ```py + // lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: [xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz] + // ``` + // + // gets formatted as: + // + // ```py + // lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: [ + // xxxxxxxxxxxxxxxxxxxx, + // yyyyyyyyyyyyyyyyyyyy, + // zzzzzzzzzzzzzzzzzzzz + // ] + // ``` + else if has_own_parentheses(body, f.context()).is_some() { + body.format().fmt(f) + } + // Finally, for expressions without their own parentheses, use + // `parenthesize_if_expands` to add parentheses around the body, only if it expands + // across multiple lines. The `Parentheses::Never` here also removes unnecessary + // parentheses around lambda bodies that fit on one line. For example: + // + // ```py + // lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: xxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyy + zzzzzzzzzzzzzzzzzzzz + // ``` + // + // is formatted as: + // + // ```py + // lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: ( + // xxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyy + zzzzzzzzzzzzzzzzzzzz + // ) + // ``` + // + // while + // + // ```py + // lambda xxxxxxxxxxxxxxxxxxxx: (xxxxxxxxxxxxxxxxxxxx + 1) + // ``` + // + // is formatted as: + // + // ```py + // lambda xxxxxxxxxxxxxxxxxxxx: xxxxxxxxxxxxxxxxxxxx + 1 + // ``` + else { + parenthesize_if_expands(&body.format().with_options(Parentheses::Never)).fmt(f) } } }