Skip to content

Commit ef4bd8d

Browse files
authored
Fix: Avoid parenthesizing subscript targets and values (#9209)
1 parent 5d41c84 commit ef4bd8d

File tree

3 files changed

+89
-14
lines changed

3 files changed

+89
-14
lines changed

crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/assignment_split_value_first.py

+17
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,23 @@
153153
function().b().c([1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3)
154154
)
155155

156+
#######
157+
# Subscripts and non-fluent attribute chains
158+
a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
159+
xxxxx
160+
].bbvbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb[
161+
yyyyyyyyyy[aaaa]
162+
] = ccccccccccccccccccccccccccccccccccc["aaaaaaa"]
163+
164+
a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
165+
xxxxx
166+
].bbvbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = ccccccccccccccccccccccccccccccccccc[
167+
"aaaaaaa"
168+
]
169+
170+
label_thresholds[label_id] = label_quantiles[label_id][
171+
min(int(tolerance * num_thresholds), num_thresholds - 1)
172+
]
156173

157174
#######
158175
# Test comment inlining

crates/ruff_python_formatter/src/statement/stmt_assign.rs

+38-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use ruff_formatter::{format_args, write, FormatError};
2-
use ruff_python_ast::{AnyNodeRef, Expr, Operator, StmtAssign, TypeParams};
2+
use ruff_python_ast::{
3+
AnyNodeRef, Expr, ExprAttribute, ExprCall, Operator, StmtAssign, TypeParams,
4+
};
35

46
use crate::builders::parenthesize_if_expands;
57
use crate::comments::{
@@ -9,7 +11,7 @@ use crate::context::{NodeLevel, WithNodeLevel};
911
use crate::expression::parentheses::{
1012
is_expression_parenthesized, NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize,
1113
};
12-
use crate::expression::{has_own_parentheses, maybe_parenthesize_expression};
14+
use crate::expression::{has_own_parentheses, has_parentheses, maybe_parenthesize_expression};
1315
use crate::prelude::*;
1416
use crate::preview::is_prefer_splitting_right_hand_side_of_assignments_enabled;
1517
use crate::statement::trailing_semicolon;
@@ -56,7 +58,7 @@ impl FormatNodeRule<StmtAssign> for FormatStmtAssign {
5658
}
5759
.fmt(f)?;
5860
}
59-
// Avoid parenthesizing the value for single-target assignments that where the
61+
// Avoid parenthesizing the value for single-target assignments where the
6062
// target has its own parentheses (list, dict, tuple, ...) and the target expands.
6163
else if has_target_own_parentheses(first, f.context())
6264
&& !is_expression_parenthesized(
@@ -193,14 +195,14 @@ impl Format<PyFormatContext<'_>> for FormatTargetWithEqualOperator<'_> {
193195
|| f.context().comments().has_trailing(self.target)
194196
{
195197
self.target.format().fmt(f)?;
196-
} else if has_target_own_parentheses(self.target, f.context()) {
198+
} else if should_parenthesize_target(self.target, f.context()) {
199+
parenthesize_if_expands(&self.target.format().with_options(Parentheses::Never))
200+
.fmt(f)?;
201+
} else {
197202
self.target
198203
.format()
199204
.with_options(Parentheses::Never)
200205
.fmt(f)?;
201-
} else {
202-
parenthesize_if_expands(&self.target.format().with_options(Parentheses::Never))
203-
.fmt(f)?;
204206
}
205207

206208
write!(f, [space(), token("="), space()])
@@ -554,7 +556,9 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
554556

555557
// For call expressions, prefer breaking after the call expression's opening parentheses
556558
// over parenthesizing the entire call expression.
557-
if value.is_call_expr() {
559+
// For subscripts, try breaking the subscript first
560+
// For attribute chains that contain any parenthesized value: Try expanding the parenthesized value first.
561+
if value.is_call_expr() || value.is_subscript_expr() || value.is_attribute_expr() {
558562
best_fitting![
559563
format_flat,
560564
// Avoid parenthesizing the call expression if the `(` fit on the line
@@ -681,11 +685,11 @@ impl Format<PyFormatContext<'_>> for AnyBeforeOperator<'_> {
681685
.fmt(f)
682686
}
683687
// Never parenthesize targets that come with their own parentheses, e.g. don't parenthesize lists or dictionary literals.
684-
else if has_target_own_parentheses(expression, f.context()) {
685-
expression.format().with_options(Parentheses::Never).fmt(f)
686-
} else {
688+
else if should_parenthesize_target(expression, f.context()) {
687689
parenthesize_if_expands(&expression.format().with_options(Parentheses::Never))
688690
.fmt(f)
691+
} else {
692+
expression.format().with_options(Parentheses::Never).fmt(f)
689693
}
690694
}
691695
// Never parenthesize type params
@@ -717,7 +721,7 @@ fn should_inline_comments(
717721
}
718722
}
719723

720-
/// Tests whether an expression that for which comments shouldn't be inlined should use the best fit layout
724+
/// Tests whether an expression for which comments shouldn't be inlined should use the best fit layout
721725
fn should_non_inlineable_use_best_fit(
722726
expr: &Expr,
723727
parent: AnyNodeRef,
@@ -728,12 +732,32 @@ fn should_non_inlineable_use_best_fit(
728732
attribute.needs_parentheses(parent, context) == OptionalParentheses::BestFit
729733
}
730734
Expr::Call(call) => call.needs_parentheses(parent, context) == OptionalParentheses::BestFit,
735+
Expr::Subscript(subscript) => {
736+
subscript.needs_parentheses(parent, context) == OptionalParentheses::BestFit
737+
}
731738
_ => false,
732739
}
733740
}
734741

735-
/// Returns `true` for targets that should not be parenthesized if they split because their expanded
736-
/// layout comes with their own set of parentheses.
742+
/// Returns `true` for targets that have their own set of parentheses when they split,
743+
/// in which case we want to avoid parenthesizing the assigned value.
737744
pub(super) fn has_target_own_parentheses(target: &Expr, context: &PyFormatContext) -> bool {
738745
matches!(target, Expr::Tuple(_)) || has_own_parentheses(target, context).is_some()
739746
}
747+
748+
pub(super) fn should_parenthesize_target(target: &Expr, context: &PyFormatContext) -> bool {
749+
!(has_target_own_parentheses(target, context)
750+
|| is_attribute_with_parenthesized_value(target, context))
751+
}
752+
753+
fn is_attribute_with_parenthesized_value(target: &Expr, context: &PyFormatContext) -> bool {
754+
match target {
755+
Expr::Attribute(ExprAttribute { value, .. }) => {
756+
has_parentheses(value.as_ref(), context).is_some()
757+
|| is_attribute_with_parenthesized_value(value, context)
758+
}
759+
Expr::Subscript(_) => true,
760+
Expr::Call(ExprCall { arguments, .. }) => !arguments.is_empty(),
761+
_ => false,
762+
}
763+
}

crates/ruff_python_formatter/tests/snapshots/format@statement__assignment_split_value_first.py.snap

+34
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,23 @@ this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use = (
159159
function().b().c([1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3)
160160
)
161161
162+
#######
163+
# Subscripts and non-fluent attribute chains
164+
a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
165+
xxxxx
166+
].bbvbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb[
167+
yyyyyyyyyy[aaaa]
168+
] = ccccccccccccccccccccccccccccccccccc["aaaaaaa"]
169+
170+
a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
171+
xxxxx
172+
].bbvbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = ccccccccccccccccccccccccccccccccccc[
173+
"aaaaaaa"
174+
]
175+
176+
label_thresholds[label_id] = label_quantiles[label_id][
177+
min(int(tolerance * num_thresholds), num_thresholds - 1)
178+
]
162179
163180
#######
164181
# Test comment inlining
@@ -394,6 +411,23 @@ this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use = (
394411
function().b().c([1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3)
395412
)
396413
414+
#######
415+
# Subscripts and non-fluent attribute chains
416+
a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
417+
xxxxx
418+
].bbvbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb[
419+
yyyyyyyyyy[aaaa]
420+
] = ccccccccccccccccccccccccccccccccccc["aaaaaaa"]
421+
422+
a = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
423+
xxxxx
424+
].bbvbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = ccccccccccccccccccccccccccccccccccc[
425+
"aaaaaaa"
426+
]
427+
428+
label_thresholds[label_id] = label_quantiles[label_id][
429+
min(int(tolerance * num_thresholds), num_thresholds - 1)
430+
]
397431
398432
#######
399433
# Test comment inlining

0 commit comments

Comments
 (0)