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

@ -0,0 +1,5 @@
[
{
"preview": "enabled"
}
]

View file

@ -0,0 +1,218 @@
#######
# Unsplittable target and value
# Only parenthesize the value if it makes it fit, otherwise avoid parentheses.
b = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvee
bbbbbbbbbbbbbbbb = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvv
# Avoid parenthesizing the value even if the target exceeds the configured width
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = bbb
############
# Splittable targets
# Does not double-parenthesize tuples
(
first_item,
second_item,
) = some_looooooooong_module.some_loooooog_function_name(
first_argument, second_argument, third_argument
)
# Preserve parentheses around the first target
(
req["ticket"]["steps"]["step"][0]["tasks"]["task"]["fields"]["field"][
"access_request"
]["destinations"]["destination"][0]["ip_address"]
) = dst
# Augmented assignment
req["ticket"]["steps"]["step"][0]["tasks"]["task"]["fields"]["field"][
"access_request"
] += dst
# Always parenthesize the value if it avoids splitting the target, regardless of the value's width.
_a: a[aaaa] = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
)
#####
# Avoid parenthesizing the value if the expression right before the `=` splits to avoid an unnecessary pair of parentheses
# The type annotation is guaranteed to split because it is too long.
_a: a[
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
# The target is too long
(
aaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
# The target splits because of a magic trailing comma
(
a,
b,
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
# The targets split because of a comment
(
# leading
a
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
(
a
# trailing
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
(
a, # nested
b
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
#######
# Multi targets
# Black always parenthesizes the right if using multiple targets regardless if the parenthesized value exceeds the
# the configured line width or not
aaaa = bbbbbbbbbbbbbbbb = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvee
)
# Black does parenthesize the target if the target itself exceeds the line width and only parenthesizes
# the values if it makes it fit.
# The second target is too long to ever fit into the configured line width.
aaaa = (
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdddd
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvee
# Does also apply for other multi target assignments, as soon as a single target exceeds the configured
# width
aaaaaa = a["aaa"] = bbbbb[aa, bbb, cccc] = dddddddddd = eeeeee = (
fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
######################
# Call expressions:
# For unsplittable targets: Parenthesize the call expression if it makes it fit.
#
# For splittable targets:
# Only parenthesize a call expression if the parens of the call don't fit on the same line
# as the target. Don't parenthesize the call expression if the target (or annotation) right before
# splits.
# Don't parenthesize the function call if the left is unsplittable.
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = a.b.function(
arg1, arg2, arg3
)
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function(
[1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3
)
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function(
[1, 2, 3],
arg1,
[1, 2, 3],
arg2,
[1, 2, 3],
arg3,
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd,
eeeeeeeeeeeeee,
)
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = (
function()
)
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = (
a.b.function(arg1, arg2, arg3)
)
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = function()
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = function(
[1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3
)
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = function(
[1, 2, 3],
arg1,
[1, 2, 3],
arg2,
[1, 2, 3],
arg3,
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd,
eeeeeeeeeeeeee,
)
####### Fluent call expressions
# Uses the regular `Multiline` layout where the entire `value` gets parenthesized
# if it doesn't fit on the line.
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use = (
function().b().c([1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3)
)
#######
# Test comment inlining
value.__dict__[key] = (
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
)
value.__dict__.keye = (
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
)
value.__dict__.keye = (
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
)
# Don't parenthesize the value because the target's trailing comma forces it to split.
a[
aaaaaaa,
b,
] = cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc # comment
# Parenthesize the value, but don't duplicate the comment.
a[aaaaaaa, b] = (
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc # comment
)
# Format both as flat, but don't loos the comment.
a[aaaaaaa, b] = bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb # comment
#######################################################
# Test the case where a parenthesized value now fits:
a[
aaaaaaa,
b
] = (
cccccccc # comment
)
# Splits the target but not the value because of the magic trailing comma.
a[
aaaaaaa,
b,
] = (
cccccccc # comment
)
# Splits the second target because of the comment and the first target because of the trailing comma.
a[
aaaaaaa,
b,
] = (
# leading comment
b
) = (
cccccccc # comment
)
########
# Type Alias Statement
type A[str, int, number] = VeryLongTypeNameThatShouldBreakFirstToTheRightBeforeSplitngtin
type A[VeryLongTypeNameThatShouldBreakFirstToTheRightBeforeSplitngtinthatExceedsTheWidth] = str

View file

@ -952,7 +952,7 @@ impl OwnParentheses {
/// Differs from [`has_own_parentheses`] in that it returns [`OwnParentheses::NonEmpty`] for /// Differs from [`has_own_parentheses`] in that it returns [`OwnParentheses::NonEmpty`] for
/// parenthesized expressions, like `(1)` or `([1])`, regardless of whether those expression have /// parenthesized expressions, like `(1)` or `([1])`, regardless of whether those expression have
/// their _own_ parentheses. /// 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); 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 // 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 { ) -> bool {
context.is_preview() 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 ruff_python_ast::StmtAnnAssign;
use crate::comments::{SourceComment, SuppressionKind}; use crate::comments::{SourceComment, SuppressionKind};
use crate::expression::has_parentheses;
use crate::prelude::*; 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; use crate::statement::trailing_semicolon;
#[derive(Default)] #[derive(Default)]
@ -19,21 +23,33 @@ impl FormatNodeRule<StmtAnnAssign> for FormatStmtAnnAssign {
simple: _, simple: _,
} = item; } = item;
write!( write!(f, [target.format(), token(":"), space()])?;
f,
[target.format(), token(":"), space(), annotation.format(),]
)?;
if let Some(value) = value { if let Some(value) = value {
write!( if is_prefer_splitting_right_hand_side_of_assignments_enabled(f.context())
f, && has_parentheses(annotation, f.context()).is_some()
[ {
space(), FormatStatementsLastExpression::RightToLeft {
token("="), before_operator: AnyBeforeOperator::Expression(annotation),
space(), operator: AnyAssignmentOperator::Assign,
FormatStatementsLastExpression::new(value, item) 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() if f.options().source_type().is_ipynb()

View file

@ -1,13 +1,17 @@
use ruff_formatter::{format_args, write, FormatError}; 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::context::{NodeLevel, WithNodeLevel};
use crate::expression::parentheses::{ 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::expression::{has_own_parentheses, maybe_parenthesize_expression};
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_prefer_splitting_right_hand_side_of_assignments_enabled;
use crate::statement::trailing_semicolon; use crate::statement::trailing_semicolon;
#[derive(Default)] #[derive(Default)]
@ -25,24 +29,66 @@ impl FormatNodeRule<StmtAssign> for FormatStmtAssign {
"Expected at least on assignment target", "Expected at least on assignment target",
))?; ))?;
write!( // The first target is special because it never gets parenthesized nor does the formatter remove parentheses if unnecessary.
f, let format_first = FormatTargetWithEqualOperator {
[ target: first,
first.format(), preserve_parentheses: true,
space(), };
token("="),
space(),
FormatTargets { targets: rest }
]
)?;
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() if f.options().source_type().is_ipynb()
&& f.context().node_level().is_last_top_level_statement() && 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() && trailing_semicolon(item.into(), f.context().source()).is_some()
&& matches!(targets.as_slice(), [Expr::Name(_)])
{ {
token(";").fmt(f)?; token(";").fmt(f)?;
} }
@ -59,6 +105,7 @@ impl FormatNodeRule<StmtAssign> for FormatStmtAssign {
} }
} }
/// Formats the targets so that they split left-to right.
#[derive(Debug)] #[derive(Debug)]
struct FormatTargets<'a> { struct FormatTargets<'a> {
targets: &'a [Expr], targets: &'a [Expr],
@ -71,7 +118,7 @@ impl Format<PyFormatContext<'_>> for FormatTargets<'_> {
let parenthesize = if comments.has_leading(first) || comments.has_trailing(first) { let parenthesize = if comments.has_leading(first) || comments.has_trailing(first) {
ParenthesizeTarget::Always ParenthesizeTarget::Always
} else if has_own_parentheses(first, f.context()).is_some() { } else if has_target_own_parentheses(first, f.context()) {
ParenthesizeTarget::Never ParenthesizeTarget::Never
} else { } else {
ParenthesizeTarget::IfBreaks ParenthesizeTarget::IfBreaks
@ -129,10 +176,50 @@ enum ParenthesizeTarget {
IfBreaks, 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). /// 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 implementation avoids parenthesizing unsplittable values (like `None`, `True`, `False`, Names, a subset of strings)
/// the trailing comment fit and inlines a trailing comment if the value itself exceeds the configured line width: /// 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: /// 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). /// * 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 /// 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. /// 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. /// 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 /// 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. /// doing so breaks the suite empty lines formatting that relies on trailing comments to be stored on the statement.
pub(super) struct FormatStatementsLastExpression<'a> { pub(super) enum FormatStatementsLastExpression<'a> {
expression: &'a Expr, /// Prefers to split what's left of `value` before splitting the value.
parent: AnyNodeRef<'a>, ///
/// ```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> { impl<'a> FormatStatementsLastExpression<'a> {
pub(super) fn new<P: Into<AnyNodeRef<'a>>>(expression: &'a Expr, parent: P) -> Self { pub(super) fn left_to_right<S: Into<AnyNodeRef<'a>>>(value: &'a Expr, statement: S) -> Self {
Self { Self::LeftToRight {
expression, value,
parent: parent.into(), statement: statement.into(),
} }
} }
} }
impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> { impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> { fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
let can_inline_comment = match self.expression { match self {
Expr::Name(_) FormatStatementsLastExpression::LeftToRight { value, statement } => {
| Expr::NoneLiteral(_) let can_inline_comment = should_inline_comments(value, *statement, f.context());
| 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,
};
if !can_inline_comment { if !can_inline_comment {
return maybe_parenthesize_expression( return maybe_parenthesize_expression(
self.expression, value,
self.parent, *statement,
Parenthesize::IfBreaks, Parenthesize::IfBreaks,
) )
.fmt(f); .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)?;
} }
Ok(()) let comments = f.context().comments().clone();
})) let expression_comments = comments.leading_dangling_trailing(*value);
.with_group_id(Some(group_id))
.fmt(f)?;
if !inline_comments.is_empty() { if let Some(inline_comments) = OptionalParenthesesInlinedComments::new(
// If the line fits into the line width, format the comments after the parenthesized expression &expression_comments,
if_group_fits_on_line(&inline_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)) .with_group_id(Some(group_id))
.fmt(f)?; .fmt(f)?;
}
Ok(()) if !inline_comments.is_empty() {
} else { // If the line fits into the line width, format the comments after the parenthesized expression
self.expression if_group_fits_on_line(&inline_comments)
.format() .with_group_id(Some(group_id))
.with_options(Parentheses::Always) .fmt(f)?;
.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> { 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 { fn is_empty(&self) -> bool {
self.expression.is_empty() && self.statement.is_empty() 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 ruff_python_ast::StmtAugAssign;
use crate::comments::{SourceComment, SuppressionKind}; use crate::comments::{SourceComment, SuppressionKind};
use crate::expression::parentheses::is_expression_parenthesized;
use crate::prelude::*; 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::statement::trailing_semicolon;
use crate::{AsFormat, FormatNodeRule}; use crate::{AsFormat, FormatNodeRule};
@ -18,17 +23,35 @@ impl FormatNodeRule<StmtAugAssign> for FormatStmtAugAssign {
value, value,
range: _, range: _,
} = item; } = item;
write!(
f, if is_prefer_splitting_right_hand_side_of_assignments_enabled(f.context())
[ && has_target_own_parentheses(target, f.context())
target.format(), && !is_expression_parenthesized(
space(), target.into(),
op.format(), f.context().comments().ranges(),
token("="), f.context().source(),
space(), )
FormatStatementsLastExpression::new(value, item) {
] 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() if f.options().source_type().is_ipynb()
&& f.context().node_level().is_last_top_level_statement() && f.context().node_level().is_last_top_level_statement()

View file

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

View file

@ -3,7 +3,10 @@ use ruff_python_ast::StmtTypeAlias;
use crate::comments::{SourceComment, SuppressionKind}; use crate::comments::{SourceComment, SuppressionKind};
use crate::prelude::*; 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)] #[derive(Default)]
pub struct FormatStmtTypeAlias; pub struct FormatStmtTypeAlias;
@ -20,6 +23,16 @@ impl FormatNodeRule<StmtTypeAlias> for FormatStmtTypeAlias {
write!(f, [token("type"), space(), name.as_ref().format()])?; write!(f, [token("type"), space(), name.as_ref().format()])?;
if let Some(type_params) = type_params { 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()])?; write!(f, [type_params.format()])?;
} }
@ -29,7 +42,7 @@ impl FormatNodeRule<StmtTypeAlias> for FormatStmtTypeAlias {
space(), space(),
token("="), token("="),
space(), space(),
FormatStatementsLastExpression::new(value, item) FormatStatementsLastExpression::left_to_right(value, item)
] ]
) )
} }

View file

@ -95,7 +95,7 @@ def f(
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -7,26 +7,16 @@ @@ -7,23 +7,13 @@
) )
# "AnnAssign"s now also work # "AnnAssign"s now also work
@ -120,16 +120,10 @@ def f(
- | Loooooooooooooooooooooooong - | Loooooooooooooooooooooooong
- | Loooooooooooooooooooooooong - | Loooooooooooooooooooooooong
-) = 7 -) = 7
-z: Short | Short2 | Short3 | Short4 = 8
-z: int = 2.3
-z: int = foo()
+z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong = 7 +z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong = 7
+z: (Short | Short2 | Short3 | Short4) = 8 z: Short | Short2 | Short3 | Short4 = 8
+z: (int) = 2.3 z: int = 2.3
+z: (int) = foo() z: int = foo()
# In case I go for not enforcing parantheses, this might get improved at the same time
x = (
@@ -63,7 +53,7 @@ @@ -63,7 +53,7 @@
@ -186,9 +180,9 @@ z: (int)
z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong = 7 z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong = 7
z: (Short | Short2 | Short3 | Short4) = 8 z: Short | Short2 | Short3 | Short4 = 8
z: (int) = 2.3 z: int = 2.3
z: (int) = foo() z: int = foo()
# In case I go for not enforcing parantheses, this might get improved at the same time # In case I go for not enforcing parantheses, this might get improved at the same time
x = ( x = (

View file

@ -789,14 +789,12 @@ log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share
- "This is a large string that has a type annotation attached to it. A type" - "This is a large string that has a type annotation attached to it. A type"
- " annotation should NOT stop a long string from being wrapped." - " annotation should NOT stop a long string from being wrapped."
-) -)
-annotated_variable: Literal["fakse_literal"] = ( +annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
annotated_variable: Literal["fakse_literal"] = (
- "This is a large string that has a type annotation attached to it. A type" - "This is a large string that has a type annotation attached to it. A type"
- " annotation should NOT stop a long string from being wrapped." - " annotation should NOT stop a long string from being wrapped."
-) + "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
+annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." )
+annotated_variable: Literal[
+ "fakse_literal"
+] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
-backslashes = ( -backslashes = (
- "This is a really long string with \"embedded\" double quotes and 'single' quotes" - "This is a really long string with \"embedded\" double quotes and 'single' quotes"
@ -1308,9 +1306,9 @@ annotated_variable: Final = (
+ "using the '+' operator." + "using the '+' operator."
) )
annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
annotated_variable: Literal[ annotated_variable: Literal["fakse_literal"] = (
"fakse_literal" "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." )
backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\" backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\"
backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\" backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\"

View file

@ -832,7 +832,7 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
some_commented_string = ( # This comment stays at the top. some_commented_string = ( # This comment stays at the top.
"This string is long but not so long that it needs hahahah toooooo be so greatttt" "This string is long but not so long that it needs hahahah toooooo be so greatttt"
@@ -279,38 +280,27 @@ @@ -279,36 +280,25 @@
) )
lpar_and_rpar_have_comments = func_call( # LPAR Comment lpar_and_rpar_have_comments = func_call( # LPAR Comment
@ -852,33 +852,31 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
- f" {'' if ID is None else ID} | perl -nE 'print if /^{field}:/'" - f" {'' if ID is None else ID} | perl -nE 'print if /^{field}:/'"
-) -)
+cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'' if ID is None else ID} | perl -nE 'print if /^{field}:/'" +cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'' if ID is None else ID} | perl -nE 'print if /^{field}:/'"
+
+cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'{{}}' if ID is None else ID} | perl -nE 'print if /^{field}:/'"
-cmd_fstring = ( -cmd_fstring = (
- "sudo -E deluge-console info --detailed --sort-reverse=time_added" - "sudo -E deluge-console info --detailed --sort-reverse=time_added"
- f" {'{{}}' if ID is None else ID} | perl -nE 'print if /^{field}:/'" - f" {'{{}}' if ID is None else ID} | perl -nE 'print if /^{field}:/'"
-) -)
+cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {{'' if ID is None else ID}} | perl -nE 'print if /^{field}:/'" +cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'{{}}' if ID is None else ID} | perl -nE 'print if /^{field}:/'"
-cmd_fstring = ( -cmd_fstring = (
- "sudo -E deluge-console info --detailed --sort-reverse=time_added {'' if ID is" - "sudo -E deluge-console info --detailed --sort-reverse=time_added {'' if ID is"
- f" None else ID}} | perl -nE 'print if /^{field}:/'" - f" None else ID}} | perl -nE 'print if /^{field}:/'"
-) -)
+fstring = f"This string really doesn't need to be an {{{{fstring}}}}, but this one most certainly, absolutely {does}." +cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {{'' if ID is None else ID}} | perl -nE 'print if /^{field}:/'"
+fstring = f"This string really doesn't need to be an {{{{fstring}}}}, but this one most certainly, absolutely {does}."
+
fstring = ( fstring = (
- "This string really doesn't need to be an {{fstring}}, but this one most" - "This string really doesn't need to be an {{fstring}}, but this one most"
- f" certainly, absolutely {does}." - f" certainly, absolutely {does}."
+ f"We have to remember to escape {braces}." " Like {these}." f" But not {this}." + f"We have to remember to escape {braces}." " Like {these}." f" But not {this}."
) )
-
-fstring = f"We have to remember to escape {braces}. Like {{these}}. But not {this}." -fstring = f"We have to remember to escape {braces}. Like {{these}}. But not {this}."
-
class A: class A:
class B:
def foo():
@@ -364,10 +354,7 @@ @@ -364,10 +354,7 @@
def foo(): def foo():
if not hasattr(module, name): if not hasattr(module, name):
@ -933,7 +931,7 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
) )
@@ -432,14 +415,12 @@ @@ -432,9 +415,7 @@
assert xxxxxxx_xxxx in [ assert xxxxxxx_xxxx in [
x.xxxxx.xxxxxx.xxxxx.xxxxxx, x.xxxxx.xxxxxx.xxxxx.xxxxxx,
x.xxxxx.xxxxxx.xxxxx.xxxx, x.xxxxx.xxxxxx.xxxxx.xxxx,
@ -943,15 +941,7 @@ s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:
+ ], "xxxxxxxxxxx xxxxxxx xxxx (xxxxxx xxxx) %x xxx xxxxx" % xxxxxxx_xxxx + ], "xxxxxxxxxxx xxxxxxx xxxx (xxxxxx xxxx) %x xxx xxxxx" % xxxxxxx_xxxx
-value.__dict__[key] = ( value.__dict__[key] = (
- "test" # set some Thrift field to non-None in the struct aa bb cc dd ee
-)
+value.__dict__[
+ key
+] = "test" # set some Thrift field to non-None in the struct aa bb cc dd ee
RE_ONE_BACKSLASH = {
"asdf_hjkl_jkl": re.compile(
@@ -449,8 +430,7 @@ @@ -449,8 +430,7 @@
RE_TWO_BACKSLASHES = { RE_TWO_BACKSLASHES = {
@ -1627,9 +1617,9 @@ class xxxxxxxxxxxxxxxxxxxxx(xxxx.xxxxxxxxxxxxx):
], "xxxxxxxxxxx xxxxxxx xxxx (xxxxxx xxxx) %x xxx xxxxx" % xxxxxxx_xxxx ], "xxxxxxxxxxx xxxxxxx xxxx (xxxxxx xxxx) %x xxx xxxxx" % xxxxxxx_xxxx
value.__dict__[ value.__dict__[key] = (
key "test" # set some Thrift field to non-None in the struct aa bb cc dd ee
] = "test" # set some Thrift field to non-None in the struct aa bb cc dd ee )
RE_ONE_BACKSLASH = { RE_ONE_BACKSLASH = {
"asdf_hjkl_jkl": re.compile( "asdf_hjkl_jkl": re.compile(

View file

@ -118,57 +118,7 @@ a = (
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -1,29 +1,31 @@ @@ -60,9 +60,7 @@
-first_item, second_item = (
- some_looooooooong_module.some_looooooooooooooong_function_name(
- first_argument, second_argument, third_argument
- )
+(
+ first_item,
+ second_item,
+) = some_looooooooong_module.some_looooooooooooooong_function_name(
+ first_argument, second_argument, third_argument
)
-some_dict["with_a_long_key"] = (
- some_looooooooong_module.some_looooooooooooooong_function_name(
- first_argument, second_argument, third_argument
- )
+some_dict[
+ "with_a_long_key"
+] = some_looooooooong_module.some_looooooooooooooong_function_name(
+ first_argument, second_argument, third_argument
)
# Make sure it works when the RHS only has one pair of (optional) parens.
-first_item, second_item = (
- some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name
-)
+(
+ first_item,
+ second_item,
+) = some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name
-some_dict["with_a_long_key"] = (
- some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name
-)
+some_dict[
+ "with_a_long_key"
+] = some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name
# Make sure chaining assignments work.
-first_item, second_item, third_item, forth_item = m["everything"] = (
- some_looooooooong_module.some_looooooooooooooong_function_name(
- first_argument, second_argument, third_argument
- )
+first_item, second_item, third_item, forth_item = m[
+ "everything"
+] = some_looooooooong_module.some_looooooooooooooong_function_name(
+ first_argument, second_argument, third_argument
)
# Make sure when the RHS's first split at the non-optional paren fits,
@@ -60,9 +62,7 @@
some_arg some_arg
).intersection(pk_cols) ).intersection(pk_cols)
@ -179,76 +129,37 @@ a = (
some_kind_of_table[ some_kind_of_table[
some_key # type: ignore # noqa: E501 some_key # type: ignore # noqa: E501
@@ -85,15 +85,29 @@
)
# Multiple targets
-a = b = (
- ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-)
+a = (
+ b
+) = ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-a = b = c = d = e = f = g = (
+a = (
+ b
+) = (
+ c
+) = (
+ d
+) = (
+ e
+) = (
+ f
+) = (
+ g
+) = (
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
-) = i = j = (
- kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
-)
+) = (
+ i
+) = (
+ j
+) = kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
a = (
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
``` ```
## Ruff Output ## Ruff Output
```python ```python
( first_item, second_item = (
first_item, some_looooooooong_module.some_looooooooooooooong_function_name(
second_item, first_argument, second_argument, third_argument
) = some_looooooooong_module.some_looooooooooooooong_function_name( )
first_argument, second_argument, third_argument
) )
some_dict[ some_dict["with_a_long_key"] = (
"with_a_long_key" some_looooooooong_module.some_looooooooooooooong_function_name(
] = some_looooooooong_module.some_looooooooooooooong_function_name( first_argument, second_argument, third_argument
first_argument, second_argument, third_argument )
) )
# Make sure it works when the RHS only has one pair of (optional) parens. # Make sure it works when the RHS only has one pair of (optional) parens.
( first_item, second_item = (
first_item, some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name
second_item, )
) = some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name
some_dict[ some_dict["with_a_long_key"] = (
"with_a_long_key" some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name
] = some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name )
# Make sure chaining assignments work. # Make sure chaining assignments work.
first_item, second_item, third_item, forth_item = m[ first_item, second_item, third_item, forth_item = m["everything"] = (
"everything" some_looooooooong_module.some_looooooooooooooong_function_name(
] = some_looooooooong_module.some_looooooooooooooong_function_name( first_argument, second_argument, third_argument
first_argument, second_argument, third_argument )
) )
# Make sure when the RHS's first split at the non-optional paren fits, # Make sure when the RHS's first split at the non-optional paren fits,
@ -308,29 +219,15 @@ some_kind_of_instance.some_kind_of_map[a_key] = (
) )
# Multiple targets # Multiple targets
a = ( a = b = (
b ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
) = ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc )
a = ( a = b = c = d = e = f = g = (
b
) = (
c
) = (
d
) = (
e
) = (
f
) = (
g
) = (
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
) = ( ) = i = j = (
i kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
) = ( )
j
) = kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
a = ( a = (
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

View file

@ -421,4 +421,44 @@ def test6():
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -72,13 +72,13 @@
## Breaking left
# Should break `[a]` first
-____[
- a
-] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c
+____[a] = (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # c
+)
-____[
- a
-] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # cc
+____[a] = (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv # cc
+)
(
# some weird comments
@@ -136,9 +136,9 @@
# 89 characters parenthesized (collapse)
____a: a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c
-_a: a[
- b
-] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c
+_a: a[b] = (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvv # c
+)
## Augmented Assign
```

View file

@ -204,17 +204,17 @@ class RemoveNewlineBeforeClassDocstring:
def f(): def f():
"""Black's `Preview.prefer_splitting_right_hand_side_of_assignments`""" """Black's `Preview.prefer_splitting_right_hand_side_of_assignments`"""
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] = (
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccc.ccccccccccccc.cccccccc
] = cccccccc.ccccccccccccc.cccccccc )
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] = (
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccc.ccccccccccccc().cccccccc
] = cccccccc.ccccccccccccc().cccccccc )
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] = (
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccc.ccccccccccccc(d).cccccccc
] = cccccccc.ccccccccccccc(d).cccccccc )
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] = ( aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb] = (
cccccccc.ccccccccccccc(d).cccccccc + e cccccccc.ccccccccccccc(d).cccccccc + e
@ -228,12 +228,12 @@ def f():
+ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
) )
self._cache: dict[ self._cache: dict[DependencyCacheKey, list[list[DependencyPackage]]] = (
DependencyCacheKey, list[list[DependencyPackage]] collections.defaultdict(list)
] = collections.defaultdict(list) )
self._cached_dependencies_by_level: dict[ self._cached_dependencies_by_level: dict[int, list[DependencyCacheKey]] = (
int, list[DependencyCacheKey] collections.defaultdict(list)
] = collections.defaultdict(list) )
``` ```

View file

@ -67,4 +67,24 @@ class DefaultRunner:
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -7,9 +7,9 @@
Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
)
-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: (
- Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
-) = Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
+ Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
+)
JSONSerializable: TypeAlias = (
"str | int | float | bool | None | list | tuple | JSONMapping"
```

View file

@ -169,4 +169,40 @@ c = b[dddddd, aaaaaa] = (
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -1,7 +1,5 @@
# break left hand side
-a1akjdshflkjahdslkfjlasfdahjlfds = (
- bakjdshflkjahdslkfjlasfdahjlfds
-) = (
+a1akjdshflkjahdslkfjlasfdahjlfds = bakjdshflkjahdslkfjlasfdahjlfds = (
cakjdshflkjahdslkfjlasfdahjlfds
) = kjaödkjaföjfahlfdalfhaöfaöfhaöfha = fkjaödkjaföjfahlfdalfhaöfaöfhaöfha = g = 3
@@ -9,15 +7,13 @@
a2 = b2 = 2
# Break the last element
-a = (
- asdf
-) = (
+a = asdf = (
fjhalsdljfalflaflapamsakjsdhflakjdslfjhalsdljfalflaflapamsakjsdhflakjdslfjhalsdljfal
) = 1
-aa = [
- bakjdshflkjahdslkfjlasfdahjlfds
-] = dddd = ddd = fkjaödkjaföjfahlfdalfhaöfaöfhaöfha = g = [3]
+aa = [bakjdshflkjahdslkfjlasfdahjlfds] = dddd = ddd = (
+ fkjaödkjaföjfahlfdalfhaöfaöfhaöfha
+) = g = [3]
aa = [] = dddd = ddd = fkjaödkjaföjfahlfdalfhaöfaöfhaöfha = g = [3]
```

View file

@ -0,0 +1,457 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/assignment_split_value_first.py
---
## Input
```python
#######
# Unsplittable target and value
# Only parenthesize the value if it makes it fit, otherwise avoid parentheses.
b = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvee
bbbbbbbbbbbbbbbb = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvv
# Avoid parenthesizing the value even if the target exceeds the configured width
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = bbb
############
# Splittable targets
# Does not double-parenthesize tuples
(
first_item,
second_item,
) = some_looooooooong_module.some_loooooog_function_name(
first_argument, second_argument, third_argument
)
# Preserve parentheses around the first target
(
req["ticket"]["steps"]["step"][0]["tasks"]["task"]["fields"]["field"][
"access_request"
]["destinations"]["destination"][0]["ip_address"]
) = dst
# Augmented assignment
req["ticket"]["steps"]["step"][0]["tasks"]["task"]["fields"]["field"][
"access_request"
] += dst
# Always parenthesize the value if it avoids splitting the target, regardless of the value's width.
_a: a[aaaa] = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
)
#####
# Avoid parenthesizing the value if the expression right before the `=` splits to avoid an unnecessary pair of parentheses
# The type annotation is guaranteed to split because it is too long.
_a: a[
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
# The target is too long
(
aaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
# The target splits because of a magic trailing comma
(
a,
b,
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
# The targets split because of a comment
(
# leading
a
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
(
a
# trailing
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
(
a, # nested
b
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
#######
# Multi targets
# Black always parenthesizes the right if using multiple targets regardless if the parenthesized value exceeds the
# the configured line width or not
aaaa = bbbbbbbbbbbbbbbb = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvee
)
# Black does parenthesize the target if the target itself exceeds the line width and only parenthesizes
# the values if it makes it fit.
# The second target is too long to ever fit into the configured line width.
aaaa = (
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdddd
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvee
# Does also apply for other multi target assignments, as soon as a single target exceeds the configured
# width
aaaaaa = a["aaa"] = bbbbb[aa, bbb, cccc] = dddddddddd = eeeeee = (
fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
######################
# Call expressions:
# For unsplittable targets: Parenthesize the call expression if it makes it fit.
#
# For splittable targets:
# Only parenthesize a call expression if the parens of the call don't fit on the same line
# as the target. Don't parenthesize the call expression if the target (or annotation) right before
# splits.
# Don't parenthesize the function call if the left is unsplittable.
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = a.b.function(
arg1, arg2, arg3
)
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function(
[1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3
)
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function(
[1, 2, 3],
arg1,
[1, 2, 3],
arg2,
[1, 2, 3],
arg3,
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd,
eeeeeeeeeeeeee,
)
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = (
function()
)
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = (
a.b.function(arg1, arg2, arg3)
)
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = function()
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = function(
[1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3
)
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = function(
[1, 2, 3],
arg1,
[1, 2, 3],
arg2,
[1, 2, 3],
arg3,
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd,
eeeeeeeeeeeeee,
)
####### Fluent call expressions
# Uses the regular `Multiline` layout where the entire `value` gets parenthesized
# if it doesn't fit on the line.
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use = (
function().b().c([1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3)
)
#######
# Test comment inlining
value.__dict__[key] = (
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
)
value.__dict__.keye = (
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
)
value.__dict__.keye = (
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
)
# Don't parenthesize the value because the target's trailing comma forces it to split.
a[
aaaaaaa,
b,
] = cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc # comment
# Parenthesize the value, but don't duplicate the comment.
a[aaaaaaa, b] = (
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc # comment
)
# Format both as flat, but don't loos the comment.
a[aaaaaaa, b] = bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb # comment
#######################################################
# Test the case where a parenthesized value now fits:
a[
aaaaaaa,
b
] = (
cccccccc # comment
)
# Splits the target but not the value because of the magic trailing comma.
a[
aaaaaaa,
b,
] = (
cccccccc # comment
)
# Splits the second target because of the comment and the first target because of the trailing comma.
a[
aaaaaaa,
b,
] = (
# leading comment
b
) = (
cccccccc # comment
)
########
# Type Alias Statement
type A[str, int, number] = VeryLongTypeNameThatShouldBreakFirstToTheRightBeforeSplitngtin
type A[VeryLongTypeNameThatShouldBreakFirstToTheRightBeforeSplitngtinthatExceedsTheWidth] = str
```
## 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 = 88
preview = Enabled
```
```python
#######
# Unsplittable target and value
# Only parenthesize the value if it makes it fit, otherwise avoid parentheses.
b = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvee
bbbbbbbbbbbbbbbb = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvv
)
# Avoid parenthesizing the value even if the target exceeds the configured width
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = bbb
############
# Splittable targets
# Does not double-parenthesize tuples
(
first_item,
second_item,
) = some_looooooooong_module.some_loooooog_function_name(
first_argument, second_argument, third_argument
)
# Preserve parentheses around the first target
(
req["ticket"]["steps"]["step"][0]["tasks"]["task"]["fields"]["field"][
"access_request"
]["destinations"]["destination"][0]["ip_address"]
) = dst
# Augmented assignment
req["ticket"]["steps"]["step"][0]["tasks"]["task"]["fields"]["field"][
"access_request"
] += dst
# Always parenthesize the value if it avoids splitting the target, regardless of the value's width.
_a: a[aaaa] = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
)
#####
# Avoid parenthesizing the value if the expression right before the `=` splits to avoid an unnecessary pair of parentheses
# The type annotation is guaranteed to split because it is too long.
_a: a[
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
# The target is too long
(
aaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
# The target splits because of a magic trailing comma
(
a,
b,
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
# The targets split because of a comment
(
# leading
a
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
(
a
# trailing
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
(
a, # nested
b,
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
#######
# Multi targets
# Black always parenthesizes the right if using multiple targets regardless if the parenthesized value exceeds the
# the configured line width or not
aaaa = bbbbbbbbbbbbbbbb = (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvee
)
# Black does parenthesize the target if the target itself exceeds the line width and only parenthesizes
# the values if it makes it fit.
# The second target is too long to ever fit into the configured line width.
aaaa = (
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdddd
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvee
# Does also apply for other multi target assignments, as soon as a single target exceeds the configured
# width
aaaaaa = a["aaa"] = bbbbb[aa, bbb, cccc] = dddddddddd = eeeeee = (
fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
######################
# Call expressions:
# For unsplittable targets: Parenthesize the call expression if it makes it fit.
#
# For splittable targets:
# Only parenthesize a call expression if the parens of the call don't fit on the same line
# as the target. Don't parenthesize the call expression if the target (or annotation) right before
# splits.
# Don't parenthesize the function call if the left is unsplittable.
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = a.b.function(
arg1, arg2, arg3
)
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function(
[1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3
)
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function(
[1, 2, 3],
arg1,
[1, 2, 3],
arg2,
[1, 2, 3],
arg3,
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd,
eeeeeeeeeeeeee,
)
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function()
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = (
a.b.function(arg1, arg2, arg3)
)
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = function()
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = function(
[1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3
)
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = function(
[1, 2, 3],
arg1,
[1, 2, 3],
arg2,
[1, 2, 3],
arg3,
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd,
eeeeeeeeeeeeee,
)
####### Fluent call expressions
# Uses the regular `Multiline` layout where the entire `value` gets parenthesized
# if it doesn't fit on the line.
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use = (
function().b().c([1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3)
)
#######
# Test comment inlining
value.__dict__[key] = (
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
)
value.__dict__.keye = (
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
)
value.__dict__.keye = (
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
)
# Don't parenthesize the value because the target's trailing comma forces it to split.
a[
aaaaaaa,
b,
] = cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc # comment
# Parenthesize the value, but don't duplicate the comment.
a[aaaaaaa, b] = (
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc # comment
)
# Format both as flat, but don't loos the comment.
a[aaaaaaa, b] = bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb # comment
#######################################################
# Test the case where a parenthesized value now fits:
a[aaaaaaa, b] = cccccccc # comment
# Splits the target but not the value because of the magic trailing comma.
a[
aaaaaaa,
b,
] = cccccccc # comment
# Splits the second target because of the comment and the first target because of the trailing comma.
a[
aaaaaaa,
b,
] = (
# leading comment
b
) = cccccccc # comment
########
# Type Alias Statement
type A[str, int, number] = (
VeryLongTypeNameThatShouldBreakFirstToTheRightBeforeSplitngtin
)
type A[
VeryLongTypeNameThatShouldBreakFirstToTheRightBeforeSplitngtinthatExceedsTheWidth
] = str
```