Preserve parentheses around partial call chains (#7109)

This commit is contained in:
Charlie Marsh 2023-09-04 10:57:04 +01:00 committed by GitHub
parent 7be28a38c5
commit ece30e7c69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 207 additions and 38 deletions

View file

@ -161,3 +161,58 @@ max_message_id = (
max_message_id = (
Message.objects.filter(recipient=recipient).order_by("id").reverse()[0].id()
)
# Parentheses with fluent style within and outside of the parentheses.
(
(
df1_aaaaaaaaaaaa.merge()
)
.groupby(1,)
.sum()
)
(
( # foo
df1_aaaaaaaaaaaa.merge()
)
.groupby(1,)
.sum()
)
(
(
df1_aaaaaaaaaaaa.merge()
.groupby(1,)
)
.sum()
)
(
(
df1_aaaaaaaaaaaa.merge()
.groupby(1,)
)
.sum()
.bar()
)
(
(
df1_aaaaaaaaaaaa.merge()
.groupby(1,)
.bar()
)
.sum()
)
(
(
df1_aaaaaaaaaaaa.merge()
.groupby(1,)
.bar()
)
.sum()
.baz()
)

View file

@ -37,49 +37,45 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
let call_chain_layout = self.call_chain_layout.apply_in_node(item, f);
let format_inner = format_with(|f: &mut PyFormatter| {
let needs_parentheses = matches!(
value.as_ref(),
Expr::Constant(ExprConstant {
value: Constant::Int(_) | Constant::Float(_),
..
})
);
let parenthesize_value =
// If the value is an integer, we need to parenthesize it to avoid a syntax error.
matches!(
value.as_ref(),
Expr::Constant(ExprConstant {
value: Constant::Int(_) | Constant::Float(_),
..
})
) || is_expression_parenthesized(value.into(), f.context().source());
if needs_parentheses {
value.format().with_options(Parentheses::Always).fmt(f)?;
} else if call_chain_layout == CallChainLayout::Fluent {
match value.as_ref() {
Expr::Attribute(expr) => {
expr.format().with_options(call_chain_layout).fmt(f)?;
}
Expr::Call(expr) => {
expr.format().with_options(call_chain_layout).fmt(f)?;
if call_chain_layout == CallChainLayout::Fluent {
// Format the dot on its own line
if call_chain_layout == CallChainLayout::Fluent {
if parenthesize_value {
// Don't propagate the call chain layout.
value.format().with_options(Parentheses::Always).fmt(f)?;
// Format the dot on its own line.
soft_line_break().fmt(f)?;
} else {
match value.as_ref() {
Expr::Attribute(expr) => {
expr.format().with_options(call_chain_layout).fmt(f)?;
}
Expr::Call(expr) => {
expr.format().with_options(call_chain_layout).fmt(f)?;
soft_line_break().fmt(f)?;
}
}
Expr::Subscript(expr) => {
expr.format().with_options(call_chain_layout).fmt(f)?;
if call_chain_layout == CallChainLayout::Fluent {
// Format the dot on its own line
Expr::Subscript(expr) => {
expr.format().with_options(call_chain_layout).fmt(f)?;
soft_line_break().fmt(f)?;
}
}
_ => {
// This matches [`CallChainLayout::from_expression`]
if is_expression_parenthesized(value.as_ref().into(), f.context().source())
{
value.format().with_options(Parentheses::Always).fmt(f)?;
// Format the dot on its own line
soft_line_break().fmt(f)?;
} else {
value.format().fmt(f)?;
_ => {
value.format().with_options(Parentheses::Never).fmt(f)?;
}
}
}
} else if parenthesize_value {
value.format().with_options(Parentheses::Always).fmt(f)?;
} else {
value.format().fmt(f)?;
value.format().with_options(Parentheses::Never).fmt(f)?;
}
// Identify dangling comments before and after the dot:

View file

@ -630,20 +630,21 @@ impl CallChainLayout {
loop {
match expr {
ExpressionRef::Attribute(ast::ExprAttribute { value, .. }) => {
expr = ExpressionRef::from(value.as_ref());
// ```
// f().g
// ^^^ value
// data[:100].T
// ^^^^^^^^^^ value
// ```
if matches!(value.as_ref(), Expr::Call(_) | Expr::Subscript(_)) {
attributes_after_parentheses += 1;
} else if is_expression_parenthesized(expr, source) {
if is_expression_parenthesized(value.into(), source) {
// `(a).b`. We preserve these parentheses so don't recurse
attributes_after_parentheses += 1;
break;
} else if matches!(value.as_ref(), Expr::Call(_) | Expr::Subscript(_)) {
attributes_after_parentheses += 1;
}
expr = ExpressionRef::from(value.as_ref());
}
// ```
// f()
@ -666,9 +667,11 @@ impl CallChainLayout {
if is_expression_parenthesized(expr, source) {
attributes_after_parentheses += 1;
}
break;
}
}
// We preserve these parentheses so don't recurse
if is_expression_parenthesized(expr, source) {
break;

View file

@ -167,6 +167,61 @@ max_message_id = (
max_message_id = (
Message.objects.filter(recipient=recipient).order_by("id").reverse()[0].id()
)
# Parentheses with fluent style within and outside of the parentheses.
(
(
df1_aaaaaaaaaaaa.merge()
)
.groupby(1,)
.sum()
)
(
( # foo
df1_aaaaaaaaaaaa.merge()
)
.groupby(1,)
.sum()
)
(
(
df1_aaaaaaaaaaaa.merge()
.groupby(1,)
)
.sum()
)
(
(
df1_aaaaaaaaaaaa.merge()
.groupby(1,)
)
.sum()
.bar()
)
(
(
df1_aaaaaaaaaaaa.merge()
.groupby(1,)
.bar()
)
.sum()
)
(
(
df1_aaaaaaaaaaaa.merge()
.groupby(1,)
.bar()
)
.sum()
.baz()
)
```
## Output
@ -350,6 +405,66 @@ max_message_id = (
max_message_id = (
Message.objects.filter(recipient=recipient).order_by("id").reverse()[0].id()
)
# Parentheses with fluent style within and outside of the parentheses.
(
(df1_aaaaaaaaaaaa.merge())
.groupby(
1,
)
.sum()
)
(
( # foo
df1_aaaaaaaaaaaa.merge()
)
.groupby(
1,
)
.sum()
)
(
(
df1_aaaaaaaaaaaa.merge().groupby(
1,
)
).sum()
)
(
(
df1_aaaaaaaaaaaa.merge().groupby(
1,
)
)
.sum()
.bar()
)
(
(
df1_aaaaaaaaaaaa.merge()
.groupby(
1,
)
.bar()
).sum()
)
(
(
df1_aaaaaaaaaaaa.merge()
.groupby(
1,
)
.bar()
)
.sum()
.baz()
)
```