consolidate layout and preview checks

This commit is contained in:
Brent Westbrook 2025-12-12 10:05:24 -05:00
parent 93a958a734
commit 17a1065fad
No known key found for this signature in database
1 changed files with 189 additions and 201 deletions

View File

@ -137,12 +137,16 @@ impl FormatNodeRule<ExprLambda> for FormatExprLambda {
write!(f, [dangling_comments(dangling)])?; write!(f, [dangling_comments(dangling)])?;
} }
FormatBody { if !preview {
body, return body.format().fmt(f);
dangling, }
layout: self.layout,
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> { struct FormatBody<'a> {
body: &'a Expr, body: &'a Expr,
dangling: &'a [SourceComment], dangling: &'a [SourceComment],
layout: ExprLambdaLayout,
} }
impl Format<PyFormatContext<'_>> for FormatBody<'_> { impl Format<PyFormatContext<'_>> for FormatBody<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
let FormatBody { let FormatBody { dangling, body } = self;
dangling,
body,
layout,
} = self;
if !is_parenthesize_lambda_bodies_enabled(f.context()) {
return body.format().fmt(f);
}
let body = *body; let body = *body;
let comments = f.context().comments().clone(); let comments = f.context().comments().clone();
let body_comments = comments.leading_dangling_trailing(body); let body_comments = comments.leading_dangling_trailing(body);
let fmt_body = format_with(|f: &mut PyFormatter| { if !dangling.is_empty() {
if !dangling.is_empty() { // Can't use partition_point because there can be additional end of line comments
// 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:
// after the initial set. All of these comments are dangling, for example: //
// // ```python
// ```python // (
// ( // lambda # 1
// lambda # 1 // # 2
// # 2 // : # 3
// : # 3 // # 4
// # 4 // y
// y // )
// ) // ```
// ``` //
// // and alternate between own line and end of line.
// and alternate between own line and end of line. let (after_parameters_end_of_line, leading_body_comments) = dangling.split_at(
let (after_parameters_end_of_line, leading_body_comments) = dangling.split_at( dangling
dangling .iter()
.iter() .position(|comment| comment.line_position().is_own_line())
.position(|comment| comment.line_position().is_own_line()) .unwrap_or(dangling.len()),
.unwrap_or(dangling.len()), );
);
// If the body is parenthesized and has its own leading comments, preserve the // If the body is parenthesized and has its own leading comments, preserve the
// separation between the dangling lambda comments and the body comments. For // separation between the dangling lambda comments and the body comments. For
// example, preserve this comment positioning: // example, preserve this comment positioning:
// //
// ```python // ```python
// ( // (
// lambda: # 1 // lambda: # 1
// # 2 // # 2
// ( # 3 // ( # 3
// x // x
// ) // )
// ) // )
// ``` // ```
// //
// 1 and 2 are dangling on the lambda and emitted first, followed by a hard line // 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. // break and the parenthesized body with its leading comments.
// //
// However, when removing 2, 1 and 3 can instead be formatted on the same line: // However, when removing 2, 1 and 3 can instead be formatted on the same line:
// //
// ```python // ```python
// ( // (
// lambda: ( # 1 # 3 // lambda: ( # 1 # 3
// x // x
// ) // )
// ) // )
// ``` // ```
let comments = f.context().comments(); let comments = f.context().comments();
if is_expression_parenthesized(body.into(), comments.ranges(), f.context().source()) if is_expression_parenthesized(body.into(), comments.ranges(), f.context().source())
&& comments.has_leading(body) && comments.has_leading(body)
{ {
trailing_comments(dangling).fmt(f)?; trailing_comments(dangling).fmt(f)?;
if leading_body_comments.is_empty() { if leading_body_comments.is_empty() {
space().fmt(f)?; space().fmt(f)?;
} else {
hard_line_break().fmt(f)?;
}
body.format().with_options(Parentheses::Always).fmt(f)
} else { } else {
write!( hard_line_break().fmt(f)?;
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(")")
]
)
} }
}
// 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) body.format().with_options(Parentheses::Always).fmt(f)
} } else {
// Calls and subscripts require special formatting because they have their own write!(
// parentheses, but they can also have an arbitrary amount of text before the f,
// 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: space(),
// token("("),
// ```py trailing_comments(after_parameters_end_of_line),
// transaction_count = self._query_txs_for_range( block_indent(&format_args!(
// get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range( leading_comments(leading_body_comments),
// chain_id=_chain_id, body.format().with_options(Parentheses::Never)
// from_ts=from_ts, )),
// to_ts=to_ts, token(")")
// ),
// )
// ```
//
// 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(")")]
] ]
.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 { best_fitting![
ExprLambdaLayout::Assignment => fits_expanded(&fmt_body).fmt(f), // body all flat
ExprLambdaLayout::Default => fmt_body.fmt(f), 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)
} }
} }
} }