Skip to content

Commit 45f6030

Browse files
authored
prefer_splitting_right_hand_side_of_assignments preview style (#8943)
1 parent 1a65e54 commit 45f6030

18 files changed

+1466
-325
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[
2+
{
3+
"preview": "enabled"
4+
}
5+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
#######
2+
# Unsplittable target and value
3+
4+
# Only parenthesize the value if it makes it fit, otherwise avoid parentheses.
5+
b = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvee
6+
7+
bbbbbbbbbbbbbbbb = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvv
8+
9+
# Avoid parenthesizing the value even if the target exceeds the configured width
10+
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = bbb
11+
12+
13+
############
14+
# Splittable targets
15+
16+
# Does not double-parenthesize tuples
17+
(
18+
first_item,
19+
second_item,
20+
) = some_looooooooong_module.some_loooooog_function_name(
21+
first_argument, second_argument, third_argument
22+
)
23+
24+
25+
# Preserve parentheses around the first target
26+
(
27+
req["ticket"]["steps"]["step"][0]["tasks"]["task"]["fields"]["field"][
28+
"access_request"
29+
]["destinations"]["destination"][0]["ip_address"]
30+
) = dst
31+
32+
# Augmented assignment
33+
req["ticket"]["steps"]["step"][0]["tasks"]["task"]["fields"]["field"][
34+
"access_request"
35+
] += dst
36+
37+
# Always parenthesize the value if it avoids splitting the target, regardless of the value's width.
38+
_a: a[aaaa] = (
39+
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
40+
)
41+
42+
#####
43+
# Avoid parenthesizing the value if the expression right before the `=` splits to avoid an unnecessary pair of parentheses
44+
45+
# The type annotation is guaranteed to split because it is too long.
46+
_a: a[
47+
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
48+
] = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
49+
50+
# The target is too long
51+
(
52+
aaaaaaaaaaa,
53+
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
54+
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvv
55+
56+
# The target splits because of a magic trailing comma
57+
(
58+
a,
59+
b,
60+
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
61+
62+
# The targets split because of a comment
63+
(
64+
# leading
65+
a
66+
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
67+
68+
(
69+
a
70+
# trailing
71+
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
72+
73+
(
74+
a, # nested
75+
b
76+
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvvvv
77+
78+
#######
79+
# Multi targets
80+
81+
# Black always parenthesizes the right if using multiple targets regardless if the parenthesized value exceeds the
82+
# the configured line width or not
83+
aaaa = bbbbbbbbbbbbbbbb = (
84+
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvee
85+
)
86+
87+
# Black does parenthesize the target if the target itself exceeds the line width and only parenthesizes
88+
# the values if it makes it fit.
89+
# The second target is too long to ever fit into the configured line width.
90+
aaaa = (
91+
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdddd
92+
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvvvvee
93+
94+
# Does also apply for other multi target assignments, as soon as a single target exceeds the configured
95+
# width
96+
aaaaaa = a["aaa"] = bbbbb[aa, bbb, cccc] = dddddddddd = eeeeee = (
97+
fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
98+
) = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
99+
100+
101+
######################
102+
# Call expressions:
103+
# For unsplittable targets: Parenthesize the call expression if it makes it fit.
104+
#
105+
# For splittable targets:
106+
# Only parenthesize a call expression if the parens of the call don't fit on the same line
107+
# as the target. Don't parenthesize the call expression if the target (or annotation) right before
108+
# splits.
109+
110+
# Don't parenthesize the function call if the left is unsplittable.
111+
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = a.b.function(
112+
arg1, arg2, arg3
113+
)
114+
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function(
115+
[1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3
116+
)
117+
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function(
118+
[1, 2, 3],
119+
arg1,
120+
[1, 2, 3],
121+
arg2,
122+
[1, 2, 3],
123+
arg3,
124+
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd,
125+
eeeeeeeeeeeeee,
126+
)
127+
128+
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = (
129+
function()
130+
)
131+
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = (
132+
a.b.function(arg1, arg2, arg3)
133+
)
134+
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = function()
135+
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = function(
136+
[1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3
137+
)
138+
this_is_a_ridiculously_long_name_and_nobodyddddddddddddddddddddddddddddddd = function(
139+
[1, 2, 3],
140+
arg1,
141+
[1, 2, 3],
142+
arg2,
143+
[1, 2, 3],
144+
arg3,
145+
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd,
146+
eeeeeeeeeeeeee,
147+
)
148+
149+
####### Fluent call expressions
150+
# Uses the regular `Multiline` layout where the entire `value` gets parenthesized
151+
# if it doesn't fit on the line.
152+
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use = (
153+
function().b().c([1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3)
154+
)
155+
156+
157+
#######
158+
# Test comment inlining
159+
value.__dict__[key] = (
160+
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
161+
)
162+
value.__dict__.keye = (
163+
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
164+
)
165+
value.__dict__.keye = (
166+
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
167+
)
168+
169+
170+
# Don't parenthesize the value because the target's trailing comma forces it to split.
171+
a[
172+
aaaaaaa,
173+
b,
174+
] = cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc # comment
175+
176+
# Parenthesize the value, but don't duplicate the comment.
177+
a[aaaaaaa, b] = (
178+
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc # comment
179+
)
180+
181+
# Format both as flat, but don't loos the comment.
182+
a[aaaaaaa, b] = bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb # comment
183+
184+
#######################################################
185+
# Test the case where a parenthesized value now fits:
186+
a[
187+
aaaaaaa,
188+
b
189+
] = (
190+
cccccccc # comment
191+
)
192+
193+
# Splits the target but not the value because of the magic trailing comma.
194+
a[
195+
aaaaaaa,
196+
b,
197+
] = (
198+
cccccccc # comment
199+
)
200+
201+
# Splits the second target because of the comment and the first target because of the trailing comma.
202+
a[
203+
aaaaaaa,
204+
b,
205+
] = (
206+
# leading comment
207+
b
208+
) = (
209+
cccccccc # comment
210+
)
211+
212+
213+
########
214+
# Type Alias Statement
215+
type A[str, int, number] = VeryLongTypeNameThatShouldBreakFirstToTheRightBeforeSplitngtin
216+
217+
type A[VeryLongTypeNameThatShouldBreakFirstToTheRightBeforeSplitngtinthatExceedsTheWidth] = str
218+

crates/ruff_python_formatter/src/expression/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -952,7 +952,7 @@ impl OwnParentheses {
952952
/// Differs from [`has_own_parentheses`] in that it returns [`OwnParentheses::NonEmpty`] for
953953
/// parenthesized expressions, like `(1)` or `([1])`, regardless of whether those expression have
954954
/// their _own_ parentheses.
955-
fn has_parentheses(expr: &Expr, context: &PyFormatContext) -> Option<OwnParentheses> {
955+
pub(crate) fn has_parentheses(expr: &Expr, context: &PyFormatContext) -> Option<OwnParentheses> {
956956
let own_parentheses = has_own_parentheses(expr, context);
957957

958958
// If the node has its own non-empty parentheses, we don't need to check for surrounding

crates/ruff_python_formatter/src/preview.rs

+7
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,10 @@ pub(crate) const fn is_hug_parens_with_braces_and_square_brackets_enabled(
1717
) -> bool {
1818
context.is_preview()
1919
}
20+
21+
/// Returns `true` if the [`prefer_splitting_right_hand_side_of_assignments`](https://github.com/astral-sh/ruff/issues/6975) preview style is enabled.
22+
pub(crate) const fn is_prefer_splitting_right_hand_side_of_assignments_enabled(
23+
context: &PyFormatContext,
24+
) -> bool {
25+
context.is_preview()
26+
}

crates/ruff_python_formatter/src/statement/stmt_ann_assign.rs

+30-14
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ use ruff_formatter::write;
22
use ruff_python_ast::StmtAnnAssign;
33

44
use crate::comments::{SourceComment, SuppressionKind};
5+
use crate::expression::has_parentheses;
56
use crate::prelude::*;
6-
use crate::statement::stmt_assign::FormatStatementsLastExpression;
7+
use crate::preview::is_prefer_splitting_right_hand_side_of_assignments_enabled;
8+
use crate::statement::stmt_assign::{
9+
AnyAssignmentOperator, AnyBeforeOperator, FormatStatementsLastExpression,
10+
};
711
use crate::statement::trailing_semicolon;
812

913
#[derive(Default)]
@@ -19,21 +23,33 @@ impl FormatNodeRule<StmtAnnAssign> for FormatStmtAnnAssign {
1923
simple: _,
2024
} = item;
2125

22-
write!(
23-
f,
24-
[target.format(), token(":"), space(), annotation.format(),]
25-
)?;
26+
write!(f, [target.format(), token(":"), space()])?;
2627

2728
if let Some(value) = value {
28-
write!(
29-
f,
30-
[
31-
space(),
32-
token("="),
33-
space(),
34-
FormatStatementsLastExpression::new(value, item)
35-
]
36-
)?;
29+
if is_prefer_splitting_right_hand_side_of_assignments_enabled(f.context())
30+
&& has_parentheses(annotation, f.context()).is_some()
31+
{
32+
FormatStatementsLastExpression::RightToLeft {
33+
before_operator: AnyBeforeOperator::Expression(annotation),
34+
operator: AnyAssignmentOperator::Assign,
35+
value,
36+
statement: item.into(),
37+
}
38+
.fmt(f)?;
39+
} else {
40+
write!(
41+
f,
42+
[
43+
annotation.format(),
44+
space(),
45+
token("="),
46+
space(),
47+
FormatStatementsLastExpression::left_to_right(value, item)
48+
]
49+
)?;
50+
}
51+
} else {
52+
annotation.format().fmt(f)?;
3753
}
3854

3955
if f.options().source_type().is_ipynb()

0 commit comments

Comments
 (0)