prefer_splitting_right_hand_side_of_assignments preview style (#8943)

This commit is contained in:
Micha Reiser 2023-12-13 12:43:23 +09:00 committed by GitHub
parent 1a65e544c5
commit 45f603000d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1473 additions and 332 deletions

View file

@ -952,7 +952,7 @@ impl OwnParentheses {
/// Differs from [`has_own_parentheses`] in that it returns [`OwnParentheses::NonEmpty`] for
/// parenthesized expressions, like `(1)` or `([1])`, regardless of whether those expression have
/// their _own_ parentheses.
fn has_parentheses(expr: &Expr, context: &PyFormatContext) -> Option<OwnParentheses> {
pub(crate) fn has_parentheses(expr: &Expr, context: &PyFormatContext) -> Option<OwnParentheses> {
let own_parentheses = has_own_parentheses(expr, context);
// If the node has its own non-empty parentheses, we don't need to check for surrounding

View file

@ -17,3 +17,10 @@ pub(crate) const fn is_hug_parens_with_braces_and_square_brackets_enabled(
) -> bool {
context.is_preview()
}
/// Returns `true` if the [`prefer_splitting_right_hand_side_of_assignments`](https://github.com/astral-sh/ruff/issues/6975) preview style is enabled.
pub(crate) const fn is_prefer_splitting_right_hand_side_of_assignments_enabled(
context: &PyFormatContext,
) -> bool {
context.is_preview()
}

View file

@ -2,8 +2,12 @@ use ruff_formatter::write;
use ruff_python_ast::StmtAnnAssign;
use crate::comments::{SourceComment, SuppressionKind};
use crate::expression::has_parentheses;
use crate::prelude::*;
use crate::statement::stmt_assign::FormatStatementsLastExpression;
use crate::preview::is_prefer_splitting_right_hand_side_of_assignments_enabled;
use crate::statement::stmt_assign::{
AnyAssignmentOperator, AnyBeforeOperator, FormatStatementsLastExpression,
};
use crate::statement::trailing_semicolon;
#[derive(Default)]
@ -19,21 +23,33 @@ impl FormatNodeRule<StmtAnnAssign> for FormatStmtAnnAssign {
simple: _,
} = item;
write!(
f,
[target.format(), token(":"), space(), annotation.format(),]
)?;
write!(f, [target.format(), token(":"), space()])?;
if let Some(value) = value {
write!(
f,
[
space(),
token("="),
space(),
FormatStatementsLastExpression::new(value, item)
]
)?;
if is_prefer_splitting_right_hand_side_of_assignments_enabled(f.context())
&& has_parentheses(annotation, f.context()).is_some()
{
FormatStatementsLastExpression::RightToLeft {
before_operator: AnyBeforeOperator::Expression(annotation),
operator: AnyAssignmentOperator::Assign,
value,
statement: item.into(),
}
.fmt(f)?;
} else {
write!(
f,
[
annotation.format(),
space(),
token("="),
space(),
FormatStatementsLastExpression::left_to_right(value, item)
]
)?;
}
} else {
annotation.format().fmt(f)?;
}
if f.options().source_type().is_ipynb()

View file

@ -1,13 +1,17 @@
use ruff_formatter::{format_args, write, FormatError};
use ruff_python_ast::{AnyNodeRef, Expr, StmtAssign};
use ruff_python_ast::{AnyNodeRef, Expr, Operator, StmtAssign, TypeParams};
use crate::comments::{trailing_comments, SourceComment, SuppressionKind};
use crate::builders::parenthesize_if_expands;
use crate::comments::{
trailing_comments, Comments, LeadingDanglingTrailingComments, SourceComment, SuppressionKind,
};
use crate::context::{NodeLevel, WithNodeLevel};
use crate::expression::parentheses::{
NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize,
is_expression_parenthesized, NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize,
};
use crate::expression::{has_own_parentheses, maybe_parenthesize_expression};
use crate::prelude::*;
use crate::preview::is_prefer_splitting_right_hand_side_of_assignments_enabled;
use crate::statement::trailing_semicolon;
#[derive(Default)]
@ -25,24 +29,66 @@ impl FormatNodeRule<StmtAssign> for FormatStmtAssign {
"Expected at least on assignment target",
))?;
write!(
f,
[
first.format(),
space(),
token("="),
space(),
FormatTargets { targets: rest }
]
)?;
// The first target is special because it never gets parenthesized nor does the formatter remove parentheses if unnecessary.
let format_first = FormatTargetWithEqualOperator {
target: first,
preserve_parentheses: true,
};
FormatStatementsLastExpression::new(value, item).fmt(f)?;
if is_prefer_splitting_right_hand_side_of_assignments_enabled(f.context()) {
// Avoid parenthesizing the value if the last target before the assigned value expands.
if let Some((last, head)) = rest.split_last() {
format_first.fmt(f)?;
for target in head {
FormatTargetWithEqualOperator {
target,
preserve_parentheses: false,
}
.fmt(f)?;
}
FormatStatementsLastExpression::RightToLeft {
before_operator: AnyBeforeOperator::Expression(last),
operator: AnyAssignmentOperator::Assign,
value,
statement: item.into(),
}
.fmt(f)?;
}
// Avoid parenthesizing the value for single-target assignments that where the
// target has its own parentheses (list, dict, tuple, ...) and the target expands.
else if has_target_own_parentheses(first, f.context())
&& !is_expression_parenthesized(
first.into(),
f.context().comments().ranges(),
f.context().source(),
)
{
FormatStatementsLastExpression::RightToLeft {
before_operator: AnyBeforeOperator::Expression(first),
operator: AnyAssignmentOperator::Assign,
value,
statement: item.into(),
}
.fmt(f)?;
}
// For single targets that have no split points, parenthesize the value only
// if it makes it fit. Otherwise omit the parentheses.
else {
format_first.fmt(f)?;
FormatStatementsLastExpression::left_to_right(value, item).fmt(f)?;
}
} else {
write!(f, [format_first, FormatTargets { targets: rest }])?;
FormatStatementsLastExpression::left_to_right(value, item).fmt(f)?;
}
if f.options().source_type().is_ipynb()
&& f.context().node_level().is_last_top_level_statement()
&& rest.is_empty()
&& first.is_name_expr()
&& trailing_semicolon(item.into(), f.context().source()).is_some()
&& matches!(targets.as_slice(), [Expr::Name(_)])
{
token(";").fmt(f)?;
}
@ -59,6 +105,7 @@ impl FormatNodeRule<StmtAssign> for FormatStmtAssign {
}
}
/// Formats the targets so that they split left-to right.
#[derive(Debug)]
struct FormatTargets<'a> {
targets: &'a [Expr],
@ -71,7 +118,7 @@ impl Format<PyFormatContext<'_>> for FormatTargets<'_> {
let parenthesize = if comments.has_leading(first) || comments.has_trailing(first) {
ParenthesizeTarget::Always
} else if has_own_parentheses(first, f.context()).is_some() {
} else if has_target_own_parentheses(first, f.context()) {
ParenthesizeTarget::Never
} else {
ParenthesizeTarget::IfBreaks
@ -129,10 +176,50 @@ enum ParenthesizeTarget {
IfBreaks,
}
/// Formats a single target with the equal operator.
struct FormatTargetWithEqualOperator<'a> {
target: &'a Expr,
/// Whether parentheses should be preserved as in the source or if the target
/// should only be parenthesized if necessary (because of comments or because it doesn't fit).
preserve_parentheses: bool,
}
impl Format<PyFormatContext<'_>> for FormatTargetWithEqualOperator<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
// Preserve parentheses for the first target or around targets with leading or trailing comments.
if self.preserve_parentheses
|| f.context().comments().has_leading(self.target)
|| f.context().comments().has_trailing(self.target)
{
self.target.format().fmt(f)?;
} else if has_target_own_parentheses(self.target, f.context()) {
self.target
.format()
.with_options(Parentheses::Never)
.fmt(f)?;
} else {
parenthesize_if_expands(&self.target.format().with_options(Parentheses::Never))
.fmt(f)?;
}
write!(f, [space(), token("="), space()])
}
}
/// Formats the last expression in statements that start with a keyword (like `return`) or after an operator (assignments).
///
/// It avoids parenthesizing unsplittable values (like `None`, `True`, `False`, Names, a subset of strings) just to make
/// the trailing comment fit and inlines a trailing comment if the value itself exceeds the configured line width:
/// The implementation avoids parenthesizing unsplittable values (like `None`, `True`, `False`, Names, a subset of strings)
/// if the value won't fit even when parenthesized.
///
/// ## Trailing comments
/// Trailing comments are inlined inside the `value`'s parentheses rather than formatted at the end
/// of the statement for unsplittable values if the `value` gets parenthesized.
///
/// Inlining the trailing comments prevent situations where the parenthesized value
/// still exceeds the configured line width, but parenthesizing helps to make the trailing comment fit.
/// Instead, it only parenthesizes `value` if it makes both the `value` and the trailing comment fit.
/// See [PR 8431](https://github.com/astral-sh/ruff/pull/8431) for more details.
///
/// The implementation formats the statement's and value's trailing end of line comments:
/// * after the expression if the expression needs no parentheses (necessary or the `expand_parent` makes the group never fit).
@ -155,123 +242,337 @@ enum ParenthesizeTarget {
/// b = short # with comment
/// ```
///
/// The long name gets parenthesized because it exceeds the configured line width and the trailing comma of the
/// The long name gets parenthesized because it exceeds the configured line width and the trailing comment of the
/// statement gets formatted inside (instead of outside) the parentheses.
///
/// The `short` name gets unparenthesized because it fits into the configured line length, regardless of whether
/// No parentheses are added for `short` because it fits into the configured line length, regardless of whether
/// the comment exceeds the line width or not.
///
/// This logic isn't implemented in [`place_comment`] by associating trailing statement comments to the expression because
/// doing so breaks the suite empty lines formatting that relies on trailing comments to be stored on the statement.
pub(super) struct FormatStatementsLastExpression<'a> {
expression: &'a Expr,
parent: AnyNodeRef<'a>,
pub(super) enum FormatStatementsLastExpression<'a> {
/// Prefers to split what's left of `value` before splitting the value.
///
/// ```python
/// aaaaaaa[bbbbbbbb] = some_long_value
/// ```
///
/// This layout splits `aaaaaaa[bbbbbbbb]` first assuming the whole statements exceeds the line width, resulting in
///
/// ```python
/// aaaaaaa[
/// bbbbbbbb
/// ] = some_long_value
/// ```
///
/// This layout is preferred over [`RightToLeft`] if the left is unsplittable (single keyword like `return` or a Name)
/// because it has better performance characteristics.
LeftToRight {
/// The right side of an assignment or the value returned in a return statement.
value: &'a Expr,
/// The parent statement that encloses the `value` expression.
statement: AnyNodeRef<'a>,
},
/// Prefers parenthesizing the value before splitting the left side. Specific to assignments.
///
/// Formats what's left of `value` together with the assignment operator and the assigned `value`.
/// This layout prefers parenthesizing the value over parenthesizing the left (target or type annotation):
///
/// ```python
/// aaaaaaa[bbbbbbbb] = some_long_value
/// ```
///
/// gets formatted to...
///
/// ```python
/// aaaaaaa[bbbbbbbb] = (
/// some_long_value
/// )
/// ```
///
/// ... regardless whether the value will fit or not.
///
/// The left only gets parenthesized if the left exceeds the configured line width on its own or
/// is forced to split because of a magical trailing comma or contains comments:
///
/// ```python
/// aaaaaaa[bbbbbbbb_exceeds_the_line_width] = some_long_value
/// ```
///
/// gets formatted to
/// ```python
/// aaaaaaa[
/// bbbbbbbb_exceeds_the_line_width
/// ] = some_long_value
/// ```
///
/// The layout avoids parenthesizing the value when the left splits to avoid
/// unnecessary parentheses. Adding the parentheses, as shown in the below example, reduces readability.
///
/// ```python
/// aaaaaaa[
/// bbbbbbbb_exceeds_the_line_width
/// ] = (
/// some_long_value
/// )
///
/// ## Non-fluent Call Expressions
/// Non-fluent call expressions in the `value` position are only parenthesized if the opening parentheses
/// exceeds the configured line length. The layout prefers splitting after the opening parentheses
/// if the `callee` expression and the opening parentheses fit.
/// fits on the line.
RightToLeft {
/// The expression that comes before the assignment operator. This is either
/// the last target, or the type annotation of an annotated assignment.
before_operator: AnyBeforeOperator<'a>,
/// The assignment operator. Either `Assign` (`=`) or the operator used by the augmented assignment statement.
operator: AnyAssignmentOperator,
/// The assigned `value`.
value: &'a Expr,
/// The assignment statement.
statement: AnyNodeRef<'a>,
},
}
impl<'a> FormatStatementsLastExpression<'a> {
pub(super) fn new<P: Into<AnyNodeRef<'a>>>(expression: &'a Expr, parent: P) -> Self {
Self {
expression,
parent: parent.into(),
pub(super) fn left_to_right<S: Into<AnyNodeRef<'a>>>(value: &'a Expr, statement: S) -> Self {
Self::LeftToRight {
value,
statement: statement.into(),
}
}
}
impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
let can_inline_comment = match self.expression {
Expr::Name(_)
| Expr::NoneLiteral(_)
| Expr::NumberLiteral(_)
| Expr::BooleanLiteral(_) => true,
Expr::StringLiteral(string) => {
string.needs_parentheses(self.parent, f.context()) == OptionalParentheses::BestFit
}
Expr::BytesLiteral(bytes) => {
bytes.needs_parentheses(self.parent, f.context()) == OptionalParentheses::BestFit
}
Expr::FString(fstring) => {
fstring.needs_parentheses(self.parent, f.context()) == OptionalParentheses::BestFit
}
_ => false,
};
match self {
FormatStatementsLastExpression::LeftToRight { value, statement } => {
let can_inline_comment = should_inline_comments(value, *statement, f.context());
if !can_inline_comment {
return maybe_parenthesize_expression(
self.expression,
self.parent,
Parenthesize::IfBreaks,
)
.fmt(f);
}
let comments = f.context().comments().clone();
let expression_comments = comments.leading_dangling_trailing(self.expression);
if expression_comments.has_leading() {
// Preserve the parentheses if the expression has any leading comments,
// same as `maybe_parenthesize_expression`
return self
.expression
.format()
.with_options(Parentheses::Always)
.fmt(f);
}
let statement_trailing_comments = comments.trailing(self.parent);
let after_end_of_line = statement_trailing_comments
.partition_point(|comment| comment.line_position().is_end_of_line());
let (stmt_inline_comments, _) = statement_trailing_comments.split_at(after_end_of_line);
let after_end_of_line = expression_comments
.trailing
.partition_point(|comment| comment.line_position().is_end_of_line());
let (expression_inline_comments, expression_trailing_comments) =
expression_comments.trailing.split_at(after_end_of_line);
if expression_trailing_comments.is_empty() {
let inline_comments = OptionalParenthesesInlinedComments {
expression: expression_inline_comments,
statement: stmt_inline_comments,
};
let group_id = f.group_id("optional_parentheses");
let f = &mut WithNodeLevel::new(NodeLevel::Expression(Some(group_id)), f);
best_fit_parenthesize(&format_with(|f| {
inline_comments.mark_formatted();
self.expression
.format()
.with_options(Parentheses::Never)
.fmt(f)?;
if !inline_comments.is_empty() {
// If the expressions exceeds the line width, format the comments in the parentheses
if_group_breaks(&inline_comments)
.with_group_id(Some(group_id))
.fmt(f)?;
if !can_inline_comment {
return maybe_parenthesize_expression(
value,
*statement,
Parenthesize::IfBreaks,
)
.fmt(f);
}
Ok(())
}))
.with_group_id(Some(group_id))
.fmt(f)?;
let comments = f.context().comments().clone();
let expression_comments = comments.leading_dangling_trailing(*value);
if !inline_comments.is_empty() {
// If the line fits into the line width, format the comments after the parenthesized expression
if_group_fits_on_line(&inline_comments)
if let Some(inline_comments) = OptionalParenthesesInlinedComments::new(
&expression_comments,
*statement,
&comments,
) {
let group_id = f.group_id("optional_parentheses");
let f = &mut WithNodeLevel::new(NodeLevel::Expression(Some(group_id)), f);
best_fit_parenthesize(&format_with(|f| {
inline_comments.mark_formatted();
value.format().with_options(Parentheses::Never).fmt(f)?;
if !inline_comments.is_empty() {
// If the expressions exceeds the line width, format the comments in the parentheses
if_group_breaks(&inline_comments).fmt(f)?;
}
Ok(())
}))
.with_group_id(Some(group_id))
.fmt(f)?;
}
Ok(())
} else {
self.expression
.format()
.with_options(Parentheses::Always)
.fmt(f)
if !inline_comments.is_empty() {
// If the line fits into the line width, format the comments after the parenthesized expression
if_group_fits_on_line(&inline_comments)
.with_group_id(Some(group_id))
.fmt(f)?;
}
Ok(())
} else {
// Preserve the parentheses if the expression has any leading or trailing comments,
// to avoid syntax errors, similar to `maybe_parenthesize_expression`.
value.format().with_options(Parentheses::Always).fmt(f)
}
}
FormatStatementsLastExpression::RightToLeft {
before_operator,
operator,
value,
statement,
} => {
let should_inline_comments = should_inline_comments(value, *statement, f.context());
// Use the normal `maybe_parenthesize_layout` for splittable `value`s.
if !should_inline_comments
&& !should_non_inlineable_use_best_fit(value, *statement, f.context())
{
return write!(
f,
[
before_operator,
space(),
operator,
space(),
maybe_parenthesize_expression(
value,
*statement,
Parenthesize::IfBreaks
)
]
);
}
let comments = f.context().comments().clone();
let expression_comments = comments.leading_dangling_trailing(*value);
// Don't inline comments for attribute and call expressions for black compatibility
let inline_comments = if should_inline_comments {
OptionalParenthesesInlinedComments::new(
&expression_comments,
*statement,
&comments,
)
} else if expression_comments.has_leading()
|| expression_comments.has_trailing_own_line()
{
None
} else {
Some(OptionalParenthesesInlinedComments::default())
};
let Some(inline_comments) = inline_comments else {
// Preserve the parentheses if the expression has any leading or trailing own line comments
// same as `maybe_parenthesize_expression`
return write!(
f,
[
before_operator,
space(),
operator,
space(),
value.format().with_options(Parentheses::Always)
]
);
};
// Prevent inline comments to be formatted as part of the expression.
inline_comments.mark_formatted();
let mut last_target = before_operator.memoized();
// Don't parenthesize the `value` if it is known that the target will break.
// This is mainly a performance optimisation that avoids unnecessary memoization
// and using the costly `BestFitting` layout if it is already known that only the last variant
// can ever fit because the left breaks.
if last_target.inspect(f)?.will_break() {
return write!(
f,
[
last_target,
space(),
operator,
space(),
value.format().with_options(Parentheses::Never),
inline_comments
]
);
}
let format_value = value.format().with_options(Parentheses::Never).memoized();
// Tries to fit the `left` and the `value` on a single line:
// ```python
// a = b = c
// ```
let format_flat = format_with(|f| {
write!(
f,
[
last_target,
space(),
operator,
space(),
format_value,
inline_comments
]
)
});
// Don't break the last assignment target but parenthesize the value to see if it fits (break right first).
//
// ```python
// a["bbbbb"] = (
// c
// )
// ```
let format_parenthesize_value = format_with(|f| {
write!(
f,
[
last_target,
space(),
operator,
space(),
token("("),
block_indent(&format_args![format_value, inline_comments]),
token(")")
]
)
});
// Fall back to parenthesizing (or splitting) the last target part if we can't make the value
// fit. Don't parenthesize the value to avoid unnecessary parentheses.
//
// ```python
// a[
// "bbbbb"
// ] = c
// ```
let format_split_left = format_with(|f| {
write!(
f,
[
last_target,
space(),
operator,
space(),
format_value,
inline_comments
]
)
});
// For call expressions, prefer breaking after the call expression's opening parentheses
// over parenthesizing the entire call expression.
if value.is_call_expr() {
best_fitting![
format_flat,
// Avoid parenthesizing the call expression if the `(` fit on the line
format_args![
last_target,
space(),
operator,
space(),
group(&format_value).should_expand(true),
],
format_parenthesize_value,
format_split_left
]
.fmt(f)
} else {
best_fitting![format_flat, format_parenthesize_value, format_split_left].fmt(f)
}
}
}
}
}
@ -283,6 +584,35 @@ struct OptionalParenthesesInlinedComments<'a> {
}
impl<'a> OptionalParenthesesInlinedComments<'a> {
fn new(
expression_comments: &LeadingDanglingTrailingComments<'a>,
statement: AnyNodeRef<'a>,
comments: &'a Comments<'a>,
) -> Option<Self> {
if expression_comments.has_leading() || expression_comments.has_trailing_own_line() {
return None;
}
let statement_trailing_comments = comments.trailing(statement);
let after_end_of_line = statement_trailing_comments
.partition_point(|comment| comment.line_position().is_end_of_line());
let (stmt_inline_comments, _) = statement_trailing_comments.split_at(after_end_of_line);
let after_end_of_line = expression_comments
.trailing
.partition_point(|comment| comment.line_position().is_end_of_line());
let (expression_inline_comments, trailing_own_line_comments) =
expression_comments.trailing.split_at(after_end_of_line);
debug_assert!(trailing_own_line_comments.is_empty(), "The method should have returned early if the expression has trailing own line comments");
Some(OptionalParenthesesInlinedComments {
expression: expression_inline_comments,
statement: stmt_inline_comments,
})
}
fn is_empty(&self) -> bool {
self.expression.is_empty() && self.statement.is_empty()
}
@ -313,3 +643,97 @@ impl Format<PyFormatContext<'_>> for OptionalParenthesesInlinedComments<'_> {
)
}
}
#[derive(Copy, Clone, Debug)]
pub(super) enum AnyAssignmentOperator {
Assign,
AugAssign(Operator),
}
impl Format<PyFormatContext<'_>> for AnyAssignmentOperator {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
match self {
AnyAssignmentOperator::Assign => token("=").fmt(f),
AnyAssignmentOperator::AugAssign(operator) => {
write!(f, [operator.format(), token("=")])
}
}
}
}
#[derive(Copy, Clone, Debug)]
pub(super) enum AnyBeforeOperator<'a> {
Expression(&'a Expr),
TypeParams(&'a TypeParams),
}
impl Format<PyFormatContext<'_>> for AnyBeforeOperator<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
match self {
AnyBeforeOperator::Expression(expression) => {
// Preserve parentheses around targets with comments.
if f.context().comments().has_leading(*expression)
|| f.context().comments().has_trailing(*expression)
{
expression
.format()
.with_options(Parentheses::Preserve)
.fmt(f)
}
// Never parenthesize targets that come with their own parentheses, e.g. don't parenthesize lists or dictionary literals.
else if has_target_own_parentheses(expression, f.context()) {
expression.format().with_options(Parentheses::Never).fmt(f)
} else {
parenthesize_if_expands(&expression.format().with_options(Parentheses::Never))
.fmt(f)
}
}
// Never parenthesize type params
AnyBeforeOperator::TypeParams(type_params) => type_params.format().fmt(f),
}
}
}
/// Returns `true` for unsplittable expressions for which comments should be inlined.
fn should_inline_comments(
expression: &Expr,
parent: AnyNodeRef,
context: &PyFormatContext,
) -> bool {
match expression {
Expr::Name(_) | Expr::NoneLiteral(_) | Expr::NumberLiteral(_) | Expr::BooleanLiteral(_) => {
true
}
Expr::StringLiteral(string) => {
string.needs_parentheses(parent, context) == OptionalParentheses::BestFit
}
Expr::BytesLiteral(bytes) => {
bytes.needs_parentheses(parent, context) == OptionalParentheses::BestFit
}
Expr::FString(fstring) => {
fstring.needs_parentheses(parent, context) == OptionalParentheses::BestFit
}
_ => false,
}
}
/// Tests whether an expression that for which comments shouldn't be inlined should use the best fit layout
fn should_non_inlineable_use_best_fit(
expr: &Expr,
parent: AnyNodeRef,
context: &PyFormatContext,
) -> bool {
match expr {
Expr::Attribute(attribute) => {
attribute.needs_parentheses(parent, context) == OptionalParentheses::BestFit
}
Expr::Call(call) => call.needs_parentheses(parent, context) == OptionalParentheses::BestFit,
_ => false,
}
}
/// Returns `true` for targets that should not be parenthesized if they split because their expanded
/// layout comes with their own set of parentheses.
pub(super) fn has_target_own_parentheses(target: &Expr, context: &PyFormatContext) -> bool {
matches!(target, Expr::Tuple(_)) || has_own_parentheses(target, context).is_some()
}

View file

@ -2,8 +2,13 @@ use ruff_formatter::write;
use ruff_python_ast::StmtAugAssign;
use crate::comments::{SourceComment, SuppressionKind};
use crate::expression::parentheses::is_expression_parenthesized;
use crate::prelude::*;
use crate::statement::stmt_assign::FormatStatementsLastExpression;
use crate::preview::is_prefer_splitting_right_hand_side_of_assignments_enabled;
use crate::statement::stmt_assign::{
has_target_own_parentheses, AnyAssignmentOperator, AnyBeforeOperator,
FormatStatementsLastExpression,
};
use crate::statement::trailing_semicolon;
use crate::{AsFormat, FormatNodeRule};
@ -18,17 +23,35 @@ impl FormatNodeRule<StmtAugAssign> for FormatStmtAugAssign {
value,
range: _,
} = item;
write!(
f,
[
target.format(),
space(),
op.format(),
token("="),
space(),
FormatStatementsLastExpression::new(value, item)
]
)?;
if is_prefer_splitting_right_hand_side_of_assignments_enabled(f.context())
&& has_target_own_parentheses(target, f.context())
&& !is_expression_parenthesized(
target.into(),
f.context().comments().ranges(),
f.context().source(),
)
{
FormatStatementsLastExpression::RightToLeft {
before_operator: AnyBeforeOperator::Expression(target),
operator: AnyAssignmentOperator::AugAssign(*op),
value,
statement: item.into(),
}
.fmt(f)?;
} else {
write!(
f,
[
target.format(),
space(),
op.format(),
token("="),
space(),
FormatStatementsLastExpression::left_to_right(value, item)
]
)?;
}
if f.options().source_type().is_ipynb()
&& f.context().node_level().is_last_top_level_statement()

View file

@ -30,7 +30,10 @@ impl FormatNodeRule<StmtReturn> for FormatStmtReturn {
Some(value) => {
write!(
f,
[space(), FormatStatementsLastExpression::new(value, item)]
[
space(),
FormatStatementsLastExpression::left_to_right(value, item)
]
)
}
None => Ok(()),

View file

@ -3,7 +3,10 @@ use ruff_python_ast::StmtTypeAlias;
use crate::comments::{SourceComment, SuppressionKind};
use crate::prelude::*;
use crate::statement::stmt_assign::FormatStatementsLastExpression;
use crate::preview::is_prefer_splitting_right_hand_side_of_assignments_enabled;
use crate::statement::stmt_assign::{
AnyAssignmentOperator, AnyBeforeOperator, FormatStatementsLastExpression,
};
#[derive(Default)]
pub struct FormatStmtTypeAlias;
@ -20,6 +23,16 @@ impl FormatNodeRule<StmtTypeAlias> for FormatStmtTypeAlias {
write!(f, [token("type"), space(), name.as_ref().format()])?;
if let Some(type_params) = type_params {
if is_prefer_splitting_right_hand_side_of_assignments_enabled(f.context()) {
return FormatStatementsLastExpression::RightToLeft {
before_operator: AnyBeforeOperator::TypeParams(type_params),
operator: AnyAssignmentOperator::Assign,
value,
statement: item.into(),
}
.fmt(f);
};
write!(f, [type_params.format()])?;
}
@ -29,7 +42,7 @@ impl FormatNodeRule<StmtTypeAlias> for FormatStmtTypeAlias {
space(),
token("="),
space(),
FormatStatementsLastExpression::new(value, item)
FormatStatementsLastExpression::left_to_right(value, item)
]
)
}