Parenthesize with statements (#5758)

<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR improves the parentheses handling for with items to get closer
to black's formatting.

### Case 1:

```python
# Black / Input
with (
    [
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "bbbbbbbbbb",
        "cccccccccccccccccccccccccccccccccccccccccc",
        dddddddddddddddddddddddddddddddd,
    ] as example1,
    aaaaaaaaaaaaaaaaaaaaaaaaaa
    + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
    + cccccccccccccccccccccccccccc
    + ddddddddddddddddd as example2,
    CtxManager2() as example2,
    CtxManager2() as example2,
    CtxManager2() as example2,
):
    ...

# Before
with (
    [
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "bbbbbbbbbb",
        "cccccccccccccccccccccccccccccccccccccccccc",
        dddddddddddddddddddddddddddddddd,
    ] as example1,
    (
        aaaaaaaaaaaaaaaaaaaaaaaaaa
        + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
        + cccccccccccccccccccccccccccc
        + ddddddddddddddddd
    ) as example2,
    CtxManager2() as example2,
    CtxManager2() as example2,
    CtxManager2() as example2,
):
    ...
```

Notice how Ruff wraps the binary expression in an extra set of
parentheses


### Case 2:
Black does not expand the with-items if the with has no parentheses:

```python
# Black / Input
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c:
    ...

# Before
with (
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c
):
    ...
```

Or 

```python
# Black / Input
with [
    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "bbbbbbbbbb",
    "cccccccccccccccccccccccccccccccccccccccccc",
    dddddddddddddddddddddddddddddddd,
] as example1, aaaaaaaaaaaaaaaaaaaaaaaaaa * bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb * cccccccccccccccccccccccccccc + ddddddddddddddddd as example2, CtxManager222222222222222() as example2:
    ...

# Before (Same as Case 1)
with (
    [
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "bbbbbbbbbb",
        "cccccccccccccccccccccccccccccccccccccccccc",
        dddddddddddddddddddddddddddddddd,
    ] as example1,
    (
        aaaaaaaaaaaaaaaaaaaaaaaaaa
        * bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
        * cccccccccccccccccccccccccccc
        + ddddddddddddddddd
    ) as example2,
    CtxManager222222222222222() as example2,
):
    ...

```
## Test Plan

I added new snapshot tests

Improves the django similarity index from 0.973 to 0.977
This commit is contained in:
Micha Reiser 2023-07-15 17:03:09 +02:00 committed by GitHub
parent e1c119fde3
commit 3cda89ecaf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 443 additions and 141 deletions

View file

@ -40,9 +40,6 @@ with (a,): # magic trailing comma
with (a): # should remove brackets with (a): # should remove brackets
... ...
# TODO: black doesn't wrap this, but maybe we want to anyway?
# if we do want to wrap, do we prefer to wrap the entire WithItem or to let the
# WithItem allow the `aa + bb` content expression to be wrapped
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c: with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c:
... ...
@ -52,3 +49,62 @@ with (name_2 for name_0 in name_4):
pass pass
with (a, *b): with (a, *b):
pass pass
with (
# leading comment
a) as b: ...
with (
# leading comment
a as b
): ...
with (a # trailing same line comment
# trailing own line comment
) as b: ...
with (
a # trailing same line comment
# trailing own line comment
as b
): ...
with (a # trailing same line comment
# trailing own line comment
) as b: ...
with (
(a
# trailing own line comment
)
as # trailing as same line comment
b # trailing b same line comment
): ...
with (
[
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccccccccccccccccccccccccccccccccccc",
dddddddddddddddddddddddddddddddd,
] as example1,
aaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ cccccccccccccccccccccccccccc
+ ddddddddddddddddd as example2,
CtxManager2() as example2,
CtxManager2() as example2,
CtxManager2() as example2,
):
...
with [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccccccccccccccccccccccccccccccccccc",
dddddddddddddddddddddddddddddddd,
] as example1, aaaaaaaaaaaaaaaaaaaaaaaaaa * bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb * cccccccccccccccccccccccccccc + ddddddddddddddddd as example2, CtxManager222222222222222() as example2:
...

View file

@ -227,10 +227,23 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
) -> &mut Self ) -> &mut Self
where where
T: Ranged, T: Ranged,
{
self.entry_with_line_separator(node, content, soft_line_break_or_space())
}
pub(crate) fn entry_with_line_separator<N, Separator>(
&mut self,
node: &N,
content: &dyn Format<PyFormatContext<'ast>>,
separator: Separator,
) -> &mut Self
where
N: Ranged,
Separator: Format<PyFormatContext<'ast>>,
{ {
self.result = self.result.and_then(|_| { self.result = self.result.and_then(|_| {
if self.end_of_last_entry.is_some() { if self.end_of_last_entry.is_some() {
write!(self.fmt, [text(","), soft_line_break_or_space()])?; write!(self.fmt, [text(","), separator])?;
} }
self.end_of_last_entry = Some(node.end()); self.end_of_last_entry = Some(node.end());

View file

@ -40,6 +40,7 @@ pub(super) fn place_comment<'a>(
handle_expr_if_comment, handle_expr_if_comment,
handle_comprehension_comment, handle_comprehension_comment,
handle_trailing_expression_starred_star_end_of_line_comment, handle_trailing_expression_starred_star_end_of_line_comment,
handle_with_item_comment,
]; ];
for handler in HANDLERS { for handler in HANDLERS {
comment = match handler(comment, locator) { comment = match handler(comment, locator) {
@ -1232,6 +1233,50 @@ fn handle_trailing_expression_starred_star_end_of_line_comment<'a>(
CommentPlacement::leading(starred.as_any_node_ref(), comment) CommentPlacement::leading(starred.as_any_node_ref(), comment)
} }
/// Handles trailing own line comments before the `as` keyword of a with item and
/// end of line comments that are on the same line as the `as` keyword:
///
/// ```python
/// with (
/// a
/// # trailing a own line comment
/// as # trailing as same line comment
/// b
// ): ...
/// ```
fn handle_with_item_comment<'a>(
comment: DecoratedComment<'a>,
locator: &Locator,
) -> CommentPlacement<'a> {
if !comment.enclosing_node().is_with_item() {
return CommentPlacement::Default(comment);
}
// Needs to be a with item with an `as` expression.
let (Some(context_expr), Some(optional_vars)) =
(comment.preceding_node(), comment.following_node())
else {
return CommentPlacement::Default(comment);
};
let as_token = find_only_token_in_range(
TextRange::new(context_expr.end(), optional_vars.start()),
locator,
TokenKind::As,
);
// If before the `as` keyword, then it must be a trailing comment of the context expression.
if comment.end() < as_token.start() {
CommentPlacement::trailing(context_expr, comment)
}
// Trailing end of line comment coming after the `as` keyword`.
else if comment.line_position().is_end_of_line() {
CommentPlacement::dangling(comment.enclosing_node(), comment)
} else {
CommentPlacement::Default(comment)
}
}
/// Looks for a token in the range that contains no other tokens except for parentheses outside /// Looks for a token in the range that contains no other tokens except for parentheses outside
/// the expression ranges /// the expression ranges
fn find_only_token_in_range(range: TextRange, locator: &Locator, token_kind: TokenKind) -> Token { fn find_only_token_in_range(range: TextRange, locator: &Locator, token_kind: TokenKind) -> Token {

View file

@ -15,24 +15,27 @@ impl FormatNodeRule<ExprAwait> for FormatExprAwait {
fn fmt_fields(&self, item: &ExprAwait, f: &mut PyFormatter) -> FormatResult<()> { fn fmt_fields(&self, item: &ExprAwait, f: &mut PyFormatter) -> FormatResult<()> {
let ExprAwait { range: _, value } = item; let ExprAwait { range: _, value } = item;
let format_value = format_with(|f: &mut PyFormatter| { write!(
if f.context().node_level().is_parenthesized() { f,
value.format().fmt(f) [
} else { text("await"),
maybe_parenthesize_expression(value, item, Parenthesize::Optional).fmt(f) space(),
} maybe_parenthesize_expression(value, item, Parenthesize::IfRequired)
}); ]
)
write!(f, [text("await"), space(), format_value])
} }
} }
impl NeedsParentheses for ExprAwait { impl NeedsParentheses for ExprAwait {
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
}
} }
} }

View file

@ -176,9 +176,13 @@ impl FormatRule<Operator, PyFormatContext<'_>> for FormatOperator {
impl NeedsParentheses for ExprBinOp { impl NeedsParentheses for ExprBinOp {
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() && !self.op.is_pow() {
OptionalParentheses::Always
} else {
OptionalParentheses::Multiline
}
} }
} }

View file

@ -159,22 +159,31 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
parenthesize, parenthesize,
} = self; } = self;
let parenthesize = match parenthesize { let comments = f.context().comments();
Parenthesize::Optional => { let preserve_parentheses = parenthesize.is_optional()
is_expression_parenthesized(AnyNodeRef::from(*expression), f.context().source()) && is_expression_parenthesized(AnyNodeRef::from(*expression), f.context().source());
let has_comments = comments.has_leading_comments(*expression)
|| comments.has_trailing_own_line_comments(*expression);
if preserve_parentheses || has_comments {
return expression.format().with_options(Parentheses::Always).fmt(f);
}
let needs_parentheses = expression.needs_parentheses(*parent, f.context());
let needs_parentheses = match parenthesize {
Parenthesize::IfRequired => {
if !needs_parentheses.is_always() && f.context().node_level().is_parenthesized() {
OptionalParentheses::Never
} else {
needs_parentheses
}
} }
Parenthesize::IfBreaks => false, Parenthesize::Optional | Parenthesize::IfBreaks => needs_parentheses,
}; };
let parentheses = match needs_parentheses {
if parenthesize || f.context().comments().has_leading_comments(*expression) { OptionalParentheses::Multiline if *parenthesize != Parenthesize::IfRequired => {
OptionalParentheses::Always
} else {
expression.needs_parentheses(*parent, f.context())
};
match parentheses {
OptionalParentheses::Multiline => {
if can_omit_optional_parentheses(expression, f.context()) { if can_omit_optional_parentheses(expression, f.context()) {
optional_parentheses(&expression.format().with_options(Parentheses::Never)) optional_parentheses(&expression.format().with_options(Parentheses::Never))
.fmt(f) .fmt(f)
@ -186,7 +195,7 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
OptionalParentheses::Always => { OptionalParentheses::Always => {
expression.format().with_options(Parentheses::Always).fmt(f) expression.format().with_options(Parentheses::Always).fmt(f)
} }
OptionalParentheses::Never => { OptionalParentheses::Never | OptionalParentheses::Multiline => {
expression.format().with_options(Parentheses::Never).fmt(f) expression.format().with_options(Parentheses::Never).fmt(f)
} }
} }

View file

@ -19,6 +19,12 @@ pub(crate) enum OptionalParentheses {
Never, Never,
} }
impl OptionalParentheses {
pub(crate) const fn is_always(self) -> bool {
matches!(self, OptionalParentheses::Always)
}
}
pub(crate) trait NeedsParentheses { pub(crate) trait NeedsParentheses {
/// Determines if this object needs optional parentheses or if it is safe to omit the parentheses. /// Determines if this object needs optional parentheses or if it is safe to omit the parentheses.
fn needs_parentheses( fn needs_parentheses(
@ -36,6 +42,17 @@ pub(crate) enum Parenthesize {
/// Parenthesizes the expression only if it doesn't fit on a line. /// Parenthesizes the expression only if it doesn't fit on a line.
IfBreaks, IfBreaks,
/// Only adds parentheses if absolutely necessary:
/// * The expression is not enclosed by another parenthesized expression and it expands over multiple lines
/// * The expression has leading or trailing comments. Adding parentheses is desired to prevent the comments from wandering.
IfRequired,
}
impl Parenthesize {
pub(crate) const fn is_optional(self) -> bool {
matches!(self, Parenthesize::Optional)
}
} }
/// Whether it is necessary to add parentheses around an expression. /// Whether it is necessary to add parentheses around an expression.

View file

@ -280,15 +280,30 @@ if True:
#[test] #[test]
fn quick_test() { fn quick_test() {
let src = r#" let src = r#"
if a * [ with (
bbbbbbbbbbbbbbbbbbbbbb, [
cccccccccccccccccccccccccccccdddddddddddddddddddddddddd, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
] + a * e * [ "bbbbbbbbbb",
ffff, "cccccccccccccccccccccccccccccccccccccccccc",
gggg, dddddddddddddddddddddddddddddddd,
hhhhhhhhhhhhhh, ] as example1,
] * c: aaaaaaaaaaaaaaaaaaaaaaaaaa
pass + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ cccccccccccccccccccccccccccc
+ ddddddddddddddddd as example2,
CtxManager2() as example2,
CtxManager2() as example2,
CtxManager2() as example2,
):
...
with [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccccccccccccccccccccccccccccccccccc",
dddddddddddddddddddddddddddddddd,
] as example1, aaaaaaaaaaaaaaaaaaaaaaaaaa * bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb * cccccccccccccccccccccccccccc + ddddddddddddddddd as example2, CtxManager222222222222222() as example2:
...
"#; "#;
// Tokenize once // Tokenize once

View file

@ -1,9 +1,12 @@
use rustpython_parser::ast::WithItem;
use ruff_formatter::{write, Buffer, FormatResult};
use crate::comments::trailing_comments;
use crate::expression::maybe_parenthesize_expression; use crate::expression::maybe_parenthesize_expression;
use crate::expression::parentheses::Parenthesize; use crate::expression::parentheses::Parenthesize;
use crate::prelude::*; use crate::prelude::*;
use crate::{FormatNodeRule, PyFormatter}; use crate::{FormatNodeRule, PyFormatter};
use ruff_formatter::{write, Buffer, FormatResult};
use rustpython_parser::ast::WithItem;
#[derive(Default)] #[derive(Default)]
pub struct FormatWithItem; pub struct FormatWithItem;
@ -16,20 +19,27 @@ impl FormatNodeRule<WithItem> for FormatWithItem {
optional_vars, optional_vars,
} = item; } = item;
let inner = format_with(|f| { let comments = f.context().comments().clone();
let trailing_as_comments = comments.dangling_comments(item);
maybe_parenthesize_expression(context_expr, item, Parenthesize::IfRequired).fmt(f)?;
if let Some(optional_vars) = optional_vars {
write!( write!(
f, f,
[maybe_parenthesize_expression( [
context_expr, space(),
item, text("as"),
Parenthesize::IfBreaks trailing_comments(trailing_as_comments),
)] space(),
optional_vars.format(),
]
)?; )?;
if let Some(optional_vars) = optional_vars { }
write!(f, [space(), text("as"), space(), optional_vars.format()])?; Ok(())
} }
Ok(())
}); fn fmt_dangling_comments(&self, _node: &WithItem, _f: &mut PyFormatter) -> FormatResult<()> {
write!(f, [group(&inner)]) Ok(())
} }
} }

View file

@ -1,11 +1,15 @@
use ruff_formatter::{write, Buffer, FormatResult};
use ruff_python_ast::node::AnyNodeRef;
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
use rustpython_parser::ast::{Ranged, StmtAsyncWith, StmtWith, Suite, WithItem}; use rustpython_parser::ast::{Ranged, StmtAsyncWith, StmtWith, Suite, WithItem};
use crate::builders::parenthesize_if_expands; use ruff_formatter::{format_args, write, FormatError};
use ruff_python_ast::node::AnyNodeRef;
use crate::comments::trailing_comments; use crate::comments::trailing_comments;
use crate::expression::parentheses::{
in_parentheses_only_soft_line_break_or_space, optional_parentheses,
};
use crate::prelude::*; use crate::prelude::*;
use crate::trivia::{SimpleTokenizer, TokenKind};
use crate::FormatNodeRule; use crate::FormatNodeRule;
pub(super) enum AnyStatementWith<'a> { pub(super) enum AnyStatementWith<'a> {
@ -68,22 +72,39 @@ impl Format<PyFormatContext<'_>> for AnyStatementWith<'_> {
let comments = f.context().comments().clone(); let comments = f.context().comments().clone();
let dangling_comments = comments.dangling_comments(self); let dangling_comments = comments.dangling_comments(self);
let joined_items = format_with(|f| { write!(
f.join_comma_separated(self.body().first().unwrap().start()) f,
.nodes(self.items().iter()) [
.finish() self.is_async()
}); .then_some(format_args![text("async"), space()]),
text("with"),
space()
]
)?;
if self.is_async() { if are_with_items_parenthesized(self, f.context())? {
write!(f, [text("async"), space()])?; optional_parentheses(&format_with(|f| {
let mut joiner = f.join_comma_separated(self.body().first().unwrap().start());
for item in self.items() {
joiner.entry_with_line_separator(
item,
&item.format(),
in_parentheses_only_soft_line_break_or_space(),
);
}
joiner.finish()
}))
.fmt(f)?;
} else {
f.join_with(format_args![text(","), space()])
.entries(self.items().iter().formatted())
.finish()?;
} }
write!( write!(
f, f,
[ [
text("with"),
space(),
group(&parenthesize_if_expands(&joined_items)),
text(":"), text(":"),
trailing_comments(dangling_comments), trailing_comments(dangling_comments),
block_indent(&self.body().format()) block_indent(&self.body().format())
@ -92,6 +113,34 @@ impl Format<PyFormatContext<'_>> for AnyStatementWith<'_> {
} }
} }
fn are_with_items_parenthesized(
with: &AnyStatementWith,
context: &PyFormatContext,
) -> FormatResult<bool> {
let first_with_item = with.items().first().ok_or(FormatError::SyntaxError)?;
let before_first_with_item = TextRange::new(with.start(), first_with_item.start());
let mut tokenizer = SimpleTokenizer::new(context.source(), before_first_with_item)
.skip_trivia()
.skip_while(|t| t.kind() == TokenKind::Async);
let with_keyword = tokenizer.next().ok_or(FormatError::SyntaxError)?;
debug_assert_eq!(
with_keyword.kind(),
TokenKind::With,
"Expected with keyword but at {with_keyword:?}"
);
match tokenizer.next() {
Some(left_paren) => {
debug_assert_eq!(left_paren.kind(), TokenKind::LParen);
Ok(true)
}
None => Ok(false),
}
}
#[derive(Default)] #[derive(Default)]
pub struct FormatStmtWith; pub struct FormatStmtWith;

View file

@ -193,9 +193,18 @@ pub(crate) enum TokenKind {
/// `in` /// `in`
In, In,
/// `as`
As,
/// `match` /// `match`
Match, Match,
/// `with`
With,
/// `async`
Async,
/// Any other non trivia token. /// Any other non trivia token.
Other, Other,
@ -272,10 +281,13 @@ impl<'a> SimpleTokenizer<'a> {
fn to_keyword_or_other(&self, range: TextRange) -> TokenKind { fn to_keyword_or_other(&self, range: TextRange) -> TokenKind {
let source = &self.source[range]; let source = &self.source[range];
match source { match source {
"if" => TokenKind::If, "as" => TokenKind::As,
"async" => TokenKind::Async,
"else" => TokenKind::Else, "else" => TokenKind::Else,
"if" => TokenKind::If,
"in" => TokenKind::In, "in" => TokenKind::In,
"match" => TokenKind::Match, // Match is a soft keyword that depends on the context but we can always lex it as a keyword and leave it to the caller (parser) to decide if it should be handled as an identifier or keyword. "match" => TokenKind::Match, // Match is a soft keyword that depends on the context but we can always lex it as a keyword and leave it to the caller (parser) to decide if it should be handled as an identifier or keyword.
"with" => TokenKind::With,
// ..., // ...,
_ => TokenKind::Other, // Potentially an identifier, but only if it isn't a string prefix. We can ignore this for now https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals _ => TokenKind::Other, // Potentially an identifier, but only if it isn't a string prefix. We can ignore this for now https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals
} }

View file

@ -93,23 +93,7 @@ async def main():
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -8,28 +8,33 @@ @@ -21,7 +21,10 @@
# Remove brackets for short coroutine/task
async def main():
- await asyncio.sleep(1)
+ await (asyncio.sleep(1))
async def main():
- await asyncio.sleep(1)
+ await (asyncio.sleep(1))
async def main():
- await asyncio.sleep(1)
+ await (asyncio.sleep(1))
# Check comments # Check comments
async def main(): async def main():
@ -121,48 +105,21 @@ async def main():
async def main(): async def main():
- await asyncio.sleep(1) # Hello @@ -78,7 +81,7 @@
+ await (
+ asyncio.sleep(1) # Hello
+ )
async def main():
- await asyncio.sleep(1) # Hello
+ await (asyncio.sleep(1)) # Hello
# Long lines
@@ -60,7 +65,7 @@
# Cr@zY Br@ck3Tz
async def main():
- await black(1)
+ await (black(1))
# Keep brackets around non power operations and nested awaits
@@ -78,16 +83,16 @@
async def main(): async def main():
- await (yield x) - await (yield x)
+ await (NOT_YET_IMPLEMENTED_ExprYield) + await NOT_YET_IMPLEMENTED_ExprYield
async def main(): async def main():
- await (await asyncio.sleep(1)) @@ -90,4 +93,4 @@
+ await (await (asyncio.sleep(1)))
async def main():
- await (await (await (await (await asyncio.sleep(1)))))
+ await (await (await (await (await (asyncio.sleep(1))))))
async def main(): async def main():
- await (yield) - await (yield)
+ await (NOT_YET_IMPLEMENTED_ExprYield) + await NOT_YET_IMPLEMENTED_ExprYield
``` ```
## Ruff Output ## Ruff Output
@ -178,15 +135,15 @@ async def main():
# Remove brackets for short coroutine/task # Remove brackets for short coroutine/task
async def main(): async def main():
await (asyncio.sleep(1)) await asyncio.sleep(1)
async def main(): async def main():
await (asyncio.sleep(1)) await asyncio.sleep(1)
async def main(): async def main():
await (asyncio.sleep(1)) await asyncio.sleep(1)
# Check comments # Check comments
@ -198,13 +155,11 @@ async def main():
async def main(): async def main():
await ( await asyncio.sleep(1) # Hello
asyncio.sleep(1) # Hello
)
async def main(): async def main():
await (asyncio.sleep(1)) # Hello await asyncio.sleep(1) # Hello
# Long lines # Long lines
@ -235,7 +190,7 @@ async def main():
# Cr@zY Br@ck3Tz # Cr@zY Br@ck3Tz
async def main(): async def main():
await (black(1)) await black(1)
# Keep brackets around non power operations and nested awaits # Keep brackets around non power operations and nested awaits
@ -253,19 +208,19 @@ async def main():
async def main(): async def main():
await (NOT_YET_IMPLEMENTED_ExprYield) await NOT_YET_IMPLEMENTED_ExprYield
async def main(): async def main():
await (await (asyncio.sleep(1))) await (await asyncio.sleep(1))
async def main(): async def main():
await (await (await (await (await (asyncio.sleep(1)))))) await (await (await (await (await asyncio.sleep(1)))))
async def main(): async def main():
await (NOT_YET_IMPLEMENTED_ExprYield) await NOT_YET_IMPLEMENTED_ExprYield
``` ```
## Black Output ## Black Output

View file

@ -46,9 +46,6 @@ with (a,): # magic trailing comma
with (a): # should remove brackets with (a): # should remove brackets
... ...
# TODO: black doesn't wrap this, but maybe we want to anyway?
# if we do want to wrap, do we prefer to wrap the entire WithItem or to let the
# WithItem allow the `aa + bb` content expression to be wrapped
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c: with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c:
... ...
@ -58,14 +55,70 @@ with (name_2 for name_0 in name_4):
pass pass
with (a, *b): with (a, *b):
pass pass
with (
# leading comment
a) as b: ...
with (
# leading comment
a as b
): ...
with (a # trailing same line comment
# trailing own line comment
) as b: ...
with (
a # trailing same line comment
# trailing own line comment
as b
): ...
with (a # trailing same line comment
# trailing own line comment
) as b: ...
with (
(a
# trailing own line comment
)
as # trailing as same line comment
b # trailing b same line comment
): ...
with (
[
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccccccccccccccccccccccccccccccccccc",
dddddddddddddddddddddddddddddddd,
] as example1,
aaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ cccccccccccccccccccccccccccc
+ ddddddddddddddddd as example2,
CtxManager2() as example2,
CtxManager2() as example2,
CtxManager2() as example2,
):
...
with [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccccccccccccccccccccccccccccccccccc",
dddddddddddddddddddddddddddddddd,
] as example1, aaaaaaaaaaaaaaaaaaaaaaaaaa * bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb * cccccccccccccccccccccccccccc + ddddddddddddddddd as example2, CtxManager222222222222222() as example2:
...
``` ```
## Output ## Output
```py ```py
with ( with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
):
... ...
# trailing # trailing
@ -105,12 +158,7 @@ with (
with a: # should remove brackets with a: # should remove brackets
... ...
# TODO: black doesn't wrap this, but maybe we want to anyway? with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c:
# if we do want to wrap, do we prefer to wrap the entire WithItem or to let the
# WithItem allow the `aa + bb` content expression to be wrapped
with (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c
):
... ...
@ -119,6 +167,72 @@ with (NOT_YET_IMPLEMENTED_generator_key for NOT_YET_IMPLEMENTED_generator_key in
pass pass
with (a, *b): with (a, *b):
pass pass
with (
# leading comment
a
) as b:
...
with (
# leading comment
a as b
):
...
with (
a # trailing same line comment
# trailing own line comment
) as b:
...
with (
a # trailing same line comment
# trailing own line comment
) as b:
...
with (
a # trailing same line comment
# trailing own line comment
) as b:
...
with (
(
a
# trailing own line comment
) as b # trailing as same line comment # trailing b same line comment
):
...
with (
[
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccccccccccccccccccccccccccccccccccc",
dddddddddddddddddddddddddddddddd,
] as example1,
aaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ cccccccccccccccccccccccccccc
+ ddddddddddddddddd as example2,
CtxManager2() as example2,
CtxManager2() as example2,
CtxManager2() as example2,
):
...
with [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbb",
"cccccccccccccccccccccccccccccccccccccccccc",
dddddddddddddddddddddddddddddddd,
] as example1, aaaaaaaaaaaaaaaaaaaaaaaaaa * bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb * cccccccccccccccccccccccccccc + ddddddddddddddddd as example2, CtxManager222222222222222() as example2:
...
``` ```