Only omit optional parentheses for starting or ending with parentheses (#8238)

This commit is contained in:
Micha Reiser 2023-10-26 15:28:58 +09:00 committed by GitHub
parent a7d1f7e1ec
commit f5e850745c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 99 additions and 21 deletions

View file

@ -180,3 +180,16 @@ if "root" not in (
): ):
msg = "Could not find root. Please try a different forest." msg = "Could not find root. Please try a different forest."
raise ValueError(msg) raise ValueError(msg)
# Regression for https://github.com/astral-sh/ruff/issues/8183
def foo():
while (
not (aaaaaaaaaaaaaaaaaaaaa(bbbbbbbb, ccccccc)) and dddddddddd < eeeeeeeeeeeeeee
):
pass
def foo():
while (
not (aaaaaaaaaaaaaaaaaaaaa[bbbbbbbb, ccccccc]) and dddddddddd < eeeeeeeeeeeeeee
):
pass

View file

@ -526,16 +526,20 @@ fn can_omit_optional_parentheses(expr: &Expr, context: &PyFormatContext) -> bool
&& has_parentheses(expr, context).is_some_and(OwnParentheses::is_non_empty) && has_parentheses(expr, context).is_some_and(OwnParentheses::is_non_empty)
} }
// Only use the layout if the first or last expression has parentheses of some sort, and // Only use the layout if the first expression starts with parentheses
// or the last expression ends with parentheses of some sort, and
// those parentheses are non-empty. // those parentheses are non-empty.
let first_parenthesized = visitor if visitor
.first
.is_some_and(|first| is_parenthesized(first, context));
let last_parenthesized = visitor
.last .last
.is_some_and(|last| is_parenthesized(last, context)); .is_some_and(|last| is_parenthesized(last, context))
{
first_parenthesized || last_parenthesized true
} else {
visitor
.first
.expression()
.is_some_and(|first| is_parenthesized(first, context))
}
} }
} }
@ -545,7 +549,7 @@ struct CanOmitOptionalParenthesesVisitor<'input> {
max_precedence_count: u32, max_precedence_count: u32,
any_parenthesized_expressions: bool, any_parenthesized_expressions: bool,
last: Option<&'input Expr>, last: Option<&'input Expr>,
first: Option<&'input Expr>, first: First<'input>,
context: &'input PyFormatContext<'input>, context: &'input PyFormatContext<'input>,
} }
@ -557,7 +561,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
max_precedence_count: 0, max_precedence_count: 0,
any_parenthesized_expressions: false, any_parenthesized_expressions: false,
last: None, last: None,
first: None, first: First::None,
} }
} }
@ -670,6 +674,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
if op.is_invert() { if op.is_invert() {
self.update_max_precedence(OperatorPrecedence::BitwiseInversion); self.update_max_precedence(OperatorPrecedence::BitwiseInversion);
} }
self.first.set_if_none(First::Token);
} }
// `[a, b].test.test[300].dot` // `[a, b].test.test[300].dot`
@ -706,17 +711,22 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
self.update_max_precedence(OperatorPrecedence::String); self.update_max_precedence(OperatorPrecedence::String);
} }
Expr::Tuple(_) // Expressions with sub expressions but a preceding token
| Expr::NamedExpr(_) // Mark this expression as first expression and not the sub expression.
| Expr::GeneratorExp(_) Expr::Lambda(_)
| Expr::Lambda(_)
| Expr::Await(_) | Expr::Await(_)
| Expr::Yield(_) | Expr::Yield(_)
| Expr::YieldFrom(_) | Expr::YieldFrom(_)
| Expr::Starred(_) => {
self.first.set_if_none(First::Token);
}
Expr::Tuple(_)
| Expr::NamedExpr(_)
| Expr::GeneratorExp(_)
| Expr::FormattedValue(_) | Expr::FormattedValue(_)
| Expr::FString(_) | Expr::FString(_)
| Expr::Constant(_) | Expr::Constant(_)
| Expr::Starred(_)
| Expr::Name(_) | Expr::Name(_)
| Expr::Slice(_) | Expr::Slice(_)
| Expr::IpyEscapeCommand(_) => {} | Expr::IpyEscapeCommand(_) => {}
@ -741,8 +751,32 @@ impl<'input> PreorderVisitor<'input> for CanOmitOptionalParenthesesVisitor<'inpu
self.visit_subexpression(expr); self.visit_subexpression(expr);
} }
if self.first.is_none() { self.first.set_if_none(First::Expression(expr));
self.first = Some(expr); }
}
#[derive(Copy, Clone, Debug)]
enum First<'a> {
None,
/// Expression starts with a non-parentheses token. E.g. `not a`
Token,
Expression(&'a Expr),
}
impl<'a> First<'a> {
#[inline]
fn set_if_none(&mut self, first: First<'a>) {
if matches!(self, First::None) {
*self = first;
}
}
fn expression(self) -> Option<&'a Expr> {
match self {
First::None | First::Token => None,
First::Expression(expr) => Some(expr),
} }
} }
} }

View file

@ -186,6 +186,19 @@ if "root" not in (
): ):
msg = "Could not find root. Please try a different forest." msg = "Could not find root. Please try a different forest."
raise ValueError(msg) raise ValueError(msg)
# Regression for https://github.com/astral-sh/ruff/issues/8183
def foo():
while (
not (aaaaaaaaaaaaaaaaaaaaa(bbbbbbbb, ccccccc)) and dddddddddd < eeeeeeeeeeeeeee
):
pass
def foo():
while (
not (aaaaaaaaaaaaaaaaaaaaa[bbbbbbbb, ccccccc]) and dddddddddd < eeeeeeeeeeeeeee
):
pass
``` ```
## Output ## Output
@ -292,10 +305,13 @@ if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & (
): ):
pass pass
if not ( if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa not (
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
) & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
)
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
pass pass
@ -383,6 +399,21 @@ if "root" not in (
): ):
msg = "Could not find root. Please try a different forest." msg = "Could not find root. Please try a different forest."
raise ValueError(msg) raise ValueError(msg)
# Regression for https://github.com/astral-sh/ruff/issues/8183
def foo():
while (
not (aaaaaaaaaaaaaaaaaaaaa(bbbbbbbb, ccccccc)) and dddddddddd < eeeeeeeeeeeeeee
):
pass
def foo():
while (
not (aaaaaaaaaaaaaaaaaaaaa[bbbbbbbb, ccccccc]) and dddddddddd < eeeeeeeeeeeeeee
):
pass
``` ```