Skip to content

Commit f5e8507

Browse files
authored
Only omit optional parentheses for starting or ending with parentheses (#8238)
1 parent a7d1f7e commit f5e8507

File tree

3 files changed

+99
-21
lines changed

3 files changed

+99
-21
lines changed

crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/unary.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,16 @@
180180
):
181181
msg = "Could not find root. Please try a different forest."
182182
raise ValueError(msg)
183+
184+
# Regression for https://github.com/astral-sh/ruff/issues/8183
185+
def foo():
186+
while (
187+
not (aaaaaaaaaaaaaaaaaaaaa(bbbbbbbb, ccccccc)) and dddddddddd < eeeeeeeeeeeeeee
188+
):
189+
pass
190+
191+
def foo():
192+
while (
193+
not (aaaaaaaaaaaaaaaaaaaaa[bbbbbbbb, ccccccc]) and dddddddddd < eeeeeeeeeeeeeee
194+
):
195+
pass

crates/ruff_python_formatter/src/expression/mod.rs

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -526,16 +526,20 @@ fn can_omit_optional_parentheses(expr: &Expr, context: &PyFormatContext) -> bool
526526
&& has_parentheses(expr, context).is_some_and(OwnParentheses::is_non_empty)
527527
}
528528

529-
// Only use the layout if the first or last expression has parentheses of some sort, and
529+
// Only use the layout if the first expression starts with parentheses
530+
// or the last expression ends with parentheses of some sort, and
530531
// those parentheses are non-empty.
531-
let first_parenthesized = visitor
532-
.first
533-
.is_some_and(|first| is_parenthesized(first, context));
534-
let last_parenthesized = visitor
532+
if visitor
535533
.last
536-
.is_some_and(|last| is_parenthesized(last, context));
537-
538-
first_parenthesized || last_parenthesized
534+
.is_some_and(|last| is_parenthesized(last, context))
535+
{
536+
true
537+
} else {
538+
visitor
539+
.first
540+
.expression()
541+
.is_some_and(|first| is_parenthesized(first, context))
542+
}
539543
}
540544
}
541545

@@ -545,7 +549,7 @@ struct CanOmitOptionalParenthesesVisitor<'input> {
545549
max_precedence_count: u32,
546550
any_parenthesized_expressions: bool,
547551
last: Option<&'input Expr>,
548-
first: Option<&'input Expr>,
552+
first: First<'input>,
549553
context: &'input PyFormatContext<'input>,
550554
}
551555

@@ -557,7 +561,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
557561
max_precedence_count: 0,
558562
any_parenthesized_expressions: false,
559563
last: None,
560-
first: None,
564+
first: First::None,
561565
}
562566
}
563567

@@ -670,6 +674,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
670674
if op.is_invert() {
671675
self.update_max_precedence(OperatorPrecedence::BitwiseInversion);
672676
}
677+
self.first.set_if_none(First::Token);
673678
}
674679

675680
// `[a, b].test.test[300].dot`
@@ -706,17 +711,22 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
706711
self.update_max_precedence(OperatorPrecedence::String);
707712
}
708713

709-
Expr::Tuple(_)
710-
| Expr::NamedExpr(_)
711-
| Expr::GeneratorExp(_)
712-
| Expr::Lambda(_)
714+
// Expressions with sub expressions but a preceding token
715+
// Mark this expression as first expression and not the sub expression.
716+
Expr::Lambda(_)
713717
| Expr::Await(_)
714718
| Expr::Yield(_)
715719
| Expr::YieldFrom(_)
720+
| Expr::Starred(_) => {
721+
self.first.set_if_none(First::Token);
722+
}
723+
724+
Expr::Tuple(_)
725+
| Expr::NamedExpr(_)
726+
| Expr::GeneratorExp(_)
716727
| Expr::FormattedValue(_)
717728
| Expr::FString(_)
718729
| Expr::Constant(_)
719-
| Expr::Starred(_)
720730
| Expr::Name(_)
721731
| Expr::Slice(_)
722732
| Expr::IpyEscapeCommand(_) => {}
@@ -741,8 +751,32 @@ impl<'input> PreorderVisitor<'input> for CanOmitOptionalParenthesesVisitor<'inpu
741751
self.visit_subexpression(expr);
742752
}
743753

744-
if self.first.is_none() {
745-
self.first = Some(expr);
754+
self.first.set_if_none(First::Expression(expr));
755+
}
756+
}
757+
758+
#[derive(Copy, Clone, Debug)]
759+
enum First<'a> {
760+
None,
761+
762+
/// Expression starts with a non-parentheses token. E.g. `not a`
763+
Token,
764+
765+
Expression(&'a Expr),
766+
}
767+
768+
impl<'a> First<'a> {
769+
#[inline]
770+
fn set_if_none(&mut self, first: First<'a>) {
771+
if matches!(self, First::None) {
772+
*self = first;
773+
}
774+
}
775+
776+
fn expression(self) -> Option<&'a Expr> {
777+
match self {
778+
First::None | First::Token => None,
779+
First::Expression(expr) => Some(expr),
746780
}
747781
}
748782
}

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

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,19 @@ if "root" not in (
186186
):
187187
msg = "Could not find root. Please try a different forest."
188188
raise ValueError(msg)
189+
190+
# Regression for https://github.com/astral-sh/ruff/issues/8183
191+
def foo():
192+
while (
193+
not (aaaaaaaaaaaaaaaaaaaaa(bbbbbbbb, ccccccc)) and dddddddddd < eeeeeeeeeeeeeee
194+
):
195+
pass
196+
197+
def foo():
198+
while (
199+
not (aaaaaaaaaaaaaaaaaaaaa[bbbbbbbb, ccccccc]) and dddddddddd < eeeeeeeeeeeeeee
200+
):
201+
pass
189202
```
190203
191204
## Output
@@ -292,10 +305,13 @@ if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & (
292305
):
293306
pass
294307
295-
if not (
296-
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
297-
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
298-
) & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
308+
if (
309+
not (
310+
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
311+
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
312+
)
313+
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
314+
):
299315
pass
300316
301317
@@ -383,6 +399,21 @@ if "root" not in (
383399
):
384400
msg = "Could not find root. Please try a different forest."
385401
raise ValueError(msg)
402+
403+
404+
# Regression for https://github.com/astral-sh/ruff/issues/8183
405+
def foo():
406+
while (
407+
not (aaaaaaaaaaaaaaaaaaaaa(bbbbbbbb, ccccccc)) and dddddddddd < eeeeeeeeeeeeeee
408+
):
409+
pass
410+
411+
412+
def foo():
413+
while (
414+
not (aaaaaaaaaaaaaaaaaaaaa[bbbbbbbb, ccccccc]) and dddddddddd < eeeeeeeeeeeeeee
415+
):
416+
pass
386417
```
387418
388419

0 commit comments

Comments
 (0)