mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-27 04:19:43 +00:00
Respect parentheses for precedence in await
(#7468)
## Summary We were using `Parenthesize::IfBreaks` universally for `await`, but dropping parentheses can change the AST due to precedence. It turns out that Black's rules aren't _exactly_ the same as operator precedence (e.g., they leave parentheses around `await ([1, 2, 3])`, although they aren't strictly required). Closes https://github.com/astral-sh/ruff/issues/7467. ## Test Plan `cargo test` No change in similarity. Before: | project | similarity index | total files | changed files | |--------------|------------------:|------------------:|------------------:| | cpython | 0.76083 | 1789 | 1632 | | django | 0.99982 | 2760 | 37 | | transformers | 0.99957 | 2587 | 398 | | twine | 1.00000 | 33 | 0 | | typeshed | 0.99983 | 3496 | 18 | | warehouse | 0.99929 | 648 | 16 | | zulip | 0.99962 | 1437 | 22 | After: | project | similarity index | total files | changed files | |--------------|------------------:|------------------:|------------------:| | cpython | 0.76083 | 1789 | 1632 | | django | 0.99982 | 2760 | 37 | | transformers | 0.99957 | 2587 | 398 | | twine | 1.00000 | 33 | 0 | | typeshed | 0.99983 | 3496 | 18 | | warehouse | 0.99929 | 648 | 16 | | zulip | 0.99962 | 1437 | 22 |
This commit is contained in:
parent
c4d85d6fb6
commit
8ab2519717
10 changed files with 165 additions and 14 deletions
|
@ -305,3 +305,21 @@ import os
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Parentheses around awaited collections are not preserved
|
||||||
|
|
||||||
|
Black preserves parentheses around awaited collections:
|
||||||
|
|
||||||
|
```python
|
||||||
|
await ([1, 2, 3])
|
||||||
|
```
|
||||||
|
|
||||||
|
Ruff will instead remove them:
|
||||||
|
|
||||||
|
```python
|
||||||
|
await [1, 2, 3]
|
||||||
|
```
|
||||||
|
|
||||||
|
This is more consistent to the formatting of other awaited expressions: Ruff and Black both
|
||||||
|
remove parentheses around, e.g., `await (1)`, only retaining them when syntactically required,
|
||||||
|
as in, e.g., `await (x := 1)`.
|
||||||
|
|
|
@ -10,3 +10,41 @@ result = await (self.request(
|
||||||
result = await (1 + f(1, 2, 3,))
|
result = await (1 + f(1, 2, 3,))
|
||||||
|
|
||||||
result = (await (1 + f(1, 2, 3,)))
|
result = (await (1 + f(1, 2, 3,)))
|
||||||
|
|
||||||
|
# Optional parentheses.
|
||||||
|
await foo
|
||||||
|
await (foo)
|
||||||
|
await foo()
|
||||||
|
await (foo())
|
||||||
|
await []()
|
||||||
|
await ([]())
|
||||||
|
await (foo + bar)()
|
||||||
|
await ((foo + bar)())
|
||||||
|
await foo.bar
|
||||||
|
await (foo.bar)
|
||||||
|
await foo['bar']
|
||||||
|
await (foo['bar'])
|
||||||
|
await 1
|
||||||
|
await (1)
|
||||||
|
await ""
|
||||||
|
await ("")
|
||||||
|
await f""
|
||||||
|
await (f"")
|
||||||
|
await [foo]
|
||||||
|
await ([foo])
|
||||||
|
await {foo}
|
||||||
|
await ({foo})
|
||||||
|
await (lambda foo: foo)
|
||||||
|
await (foo or bar)
|
||||||
|
await (foo * bar)
|
||||||
|
await (yield foo)
|
||||||
|
await (not foo)
|
||||||
|
await 1, 2, 3
|
||||||
|
await (1, 2, 3)
|
||||||
|
await ( # comment
|
||||||
|
[foo]
|
||||||
|
)
|
||||||
|
await (
|
||||||
|
# comment
|
||||||
|
[foo]
|
||||||
|
)
|
||||||
|
|
|
@ -33,7 +33,7 @@ impl NeedsParentheses for ExprBinOp {
|
||||||
parent: AnyNodeRef,
|
parent: AnyNodeRef,
|
||||||
context: &PyFormatContext,
|
context: &PyFormatContext,
|
||||||
) -> OptionalParentheses {
|
) -> OptionalParentheses {
|
||||||
if parent.is_expr_await() && !self.op.is_pow() {
|
if parent.is_expr_await() {
|
||||||
OptionalParentheses::Always
|
OptionalParentheses::Always
|
||||||
} else if let Expr::Constant(constant) = self.left.as_ref() {
|
} else if let Expr::Constant(constant) = self.left.as_ref() {
|
||||||
// Multiline strings are guaranteed to never fit, avoid adding unnecessary parentheses
|
// Multiline strings are guaranteed to never fit, avoid adding unnecessary parentheses
|
||||||
|
|
|
@ -19,10 +19,14 @@ impl FormatNodeRule<ExprBoolOp> for FormatExprBoolOp {
|
||||||
impl NeedsParentheses for ExprBoolOp {
|
impl NeedsParentheses for ExprBoolOp {
|
||||||
fn needs_parentheses(
|
fn needs_parentheses(
|
||||||
&self,
|
&self,
|
||||||
_parent: AnyNodeRef,
|
parent: AnyNodeRef,
|
||||||
_context: &PyFormatContext,
|
_context: &PyFormatContext,
|
||||||
) -> OptionalParentheses {
|
) -> OptionalParentheses {
|
||||||
OptionalParentheses::Multiline
|
if parent.is_expr_await() {
|
||||||
|
OptionalParentheses::Always
|
||||||
|
} else {
|
||||||
|
OptionalParentheses::Multiline
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,12 @@ impl FormatNodeRule<ExprCompare> for FormatExprCompare {
|
||||||
impl NeedsParentheses for ExprCompare {
|
impl NeedsParentheses for ExprCompare {
|
||||||
fn needs_parentheses(
|
fn needs_parentheses(
|
||||||
&self,
|
&self,
|
||||||
_parent: AnyNodeRef,
|
parent: AnyNodeRef,
|
||||||
context: &PyFormatContext,
|
context: &PyFormatContext,
|
||||||
) -> OptionalParentheses {
|
) -> OptionalParentheses {
|
||||||
if let Expr::Constant(constant) = self.left.as_ref() {
|
if parent.is_expr_await() {
|
||||||
|
OptionalParentheses::Always
|
||||||
|
} else if let Expr::Constant(constant) = self.left.as_ref() {
|
||||||
// Multiline strings are guaranteed to never fit, avoid adding unnecessary parentheses
|
// Multiline strings are guaranteed to never fit, avoid adding unnecessary parentheses
|
||||||
if !constant.value.is_implicit_concatenated()
|
if !constant.value.is_implicit_concatenated()
|
||||||
&& is_multiline_string(constant, context.source())
|
&& is_multiline_string(constant, context.source())
|
||||||
|
|
|
@ -91,10 +91,14 @@ impl FormatNodeRule<ExprGeneratorExp> for FormatExprGeneratorExp {
|
||||||
impl NeedsParentheses for ExprGeneratorExp {
|
impl NeedsParentheses for ExprGeneratorExp {
|
||||||
fn needs_parentheses(
|
fn needs_parentheses(
|
||||||
&self,
|
&self,
|
||||||
_parent: AnyNodeRef,
|
parent: AnyNodeRef,
|
||||||
_context: &PyFormatContext,
|
_context: &PyFormatContext,
|
||||||
) -> OptionalParentheses {
|
) -> OptionalParentheses {
|
||||||
OptionalParentheses::Never
|
if parent.is_expr_await() {
|
||||||
|
OptionalParentheses::Always
|
||||||
|
} else {
|
||||||
|
OptionalParentheses::Never
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,10 +85,14 @@ impl FormatNodeRule<ExprIfExp> for FormatExprIfExp {
|
||||||
impl NeedsParentheses for ExprIfExp {
|
impl NeedsParentheses for ExprIfExp {
|
||||||
fn needs_parentheses(
|
fn needs_parentheses(
|
||||||
&self,
|
&self,
|
||||||
_parent: AnyNodeRef,
|
parent: AnyNodeRef,
|
||||||
_context: &PyFormatContext,
|
_context: &PyFormatContext,
|
||||||
) -> OptionalParentheses {
|
) -> OptionalParentheses {
|
||||||
OptionalParentheses::Multiline
|
if parent.is_expr_await() {
|
||||||
|
OptionalParentheses::Always
|
||||||
|
} else {
|
||||||
|
OptionalParentheses::Multiline
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,9 +64,13 @@ impl FormatNodeRule<ExprLambda> for FormatExprLambda {
|
||||||
impl NeedsParentheses for ExprLambda {
|
impl NeedsParentheses for ExprLambda {
|
||||||
fn needs_parentheses(
|
fn needs_parentheses(
|
||||||
&self,
|
&self,
|
||||||
_parent: AnyNodeRef,
|
parent: AnyNodeRef,
|
||||||
_context: &PyFormatContext,
|
_context: &PyFormatContext,
|
||||||
) -> OptionalParentheses {
|
) -> OptionalParentheses {
|
||||||
OptionalParentheses::Multiline
|
if parent.is_expr_await() {
|
||||||
|
OptionalParentheses::Always
|
||||||
|
} else {
|
||||||
|
OptionalParentheses::Multiline
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,11 +72,12 @@ impl FormatNodeRule<ExprUnaryOp> for FormatExprUnaryOp {
|
||||||
impl NeedsParentheses for ExprUnaryOp {
|
impl NeedsParentheses for ExprUnaryOp {
|
||||||
fn needs_parentheses(
|
fn needs_parentheses(
|
||||||
&self,
|
&self,
|
||||||
_parent: AnyNodeRef,
|
parent: AnyNodeRef,
|
||||||
context: &PyFormatContext,
|
context: &PyFormatContext,
|
||||||
) -> OptionalParentheses {
|
) -> OptionalParentheses {
|
||||||
// We preserve the parentheses of the operand. It should not be necessary to break this expression.
|
if parent.is_expr_await() {
|
||||||
if is_expression_parenthesized(
|
OptionalParentheses::Always
|
||||||
|
} else if is_expression_parenthesized(
|
||||||
self.operand.as_ref().into(),
|
self.operand.as_ref().into(),
|
||||||
context.comments().ranges(),
|
context.comments().ranges(),
|
||||||
context.source(),
|
context.source(),
|
||||||
|
|
|
@ -16,6 +16,44 @@ result = await (self.request(
|
||||||
result = await (1 + f(1, 2, 3,))
|
result = await (1 + f(1, 2, 3,))
|
||||||
|
|
||||||
result = (await (1 + f(1, 2, 3,)))
|
result = (await (1 + f(1, 2, 3,)))
|
||||||
|
|
||||||
|
# Optional parentheses.
|
||||||
|
await foo
|
||||||
|
await (foo)
|
||||||
|
await foo()
|
||||||
|
await (foo())
|
||||||
|
await []()
|
||||||
|
await ([]())
|
||||||
|
await (foo + bar)()
|
||||||
|
await ((foo + bar)())
|
||||||
|
await foo.bar
|
||||||
|
await (foo.bar)
|
||||||
|
await foo['bar']
|
||||||
|
await (foo['bar'])
|
||||||
|
await 1
|
||||||
|
await (1)
|
||||||
|
await ""
|
||||||
|
await ("")
|
||||||
|
await f""
|
||||||
|
await (f"")
|
||||||
|
await [foo]
|
||||||
|
await ([foo])
|
||||||
|
await {foo}
|
||||||
|
await ({foo})
|
||||||
|
await (lambda foo: foo)
|
||||||
|
await (foo or bar)
|
||||||
|
await (foo * bar)
|
||||||
|
await (yield foo)
|
||||||
|
await (not foo)
|
||||||
|
await 1, 2, 3
|
||||||
|
await (1, 2, 3)
|
||||||
|
await ( # comment
|
||||||
|
[foo]
|
||||||
|
)
|
||||||
|
await (
|
||||||
|
# comment
|
||||||
|
[foo]
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
|
@ -46,6 +84,44 @@ result = await (
|
||||||
3,
|
3,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Optional parentheses.
|
||||||
|
await foo
|
||||||
|
await foo
|
||||||
|
await foo()
|
||||||
|
await foo()
|
||||||
|
await []()
|
||||||
|
await []()
|
||||||
|
await (foo + bar)()
|
||||||
|
await (foo + bar)()
|
||||||
|
await foo.bar
|
||||||
|
await foo.bar
|
||||||
|
await foo["bar"]
|
||||||
|
await foo["bar"]
|
||||||
|
await 1
|
||||||
|
await 1
|
||||||
|
await ""
|
||||||
|
await ""
|
||||||
|
await f""
|
||||||
|
await f""
|
||||||
|
await [foo]
|
||||||
|
await [foo]
|
||||||
|
await {foo}
|
||||||
|
await {foo}
|
||||||
|
await (lambda foo: foo)
|
||||||
|
await (foo or bar)
|
||||||
|
await (foo * bar)
|
||||||
|
await (yield foo)
|
||||||
|
await (not foo)
|
||||||
|
await 1, 2, 3
|
||||||
|
await (1, 2, 3)
|
||||||
|
await ( # comment
|
||||||
|
[foo]
|
||||||
|
)
|
||||||
|
await (
|
||||||
|
# comment
|
||||||
|
[foo]
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue