Consistently format async statements similar to their non-async version. (#3609)

This commit is contained in:
Yilei "Dolee" Yang 2023-03-16 13:31:27 -07:00 committed by GitHub
parent 71a2daaacf
commit fc6cea0f0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 103 additions and 8 deletions

View File

@ -16,6 +16,8 @@
- Add trailing commas to collection literals even if there's a comment after the last - Add trailing commas to collection literals even if there's a comment after the last
entry (#3393) entry (#3393)
- `async def`, `async for`, and `async with` statements are now formatted consistently
compared to their non-async version. (#3609)
- `with` statements that contain two context managers will be consistently wrapped in - `with` statements that contain two context managers will be consistently wrapped in
parentheses (#3589) parentheses (#3589)

View File

@ -36,6 +36,7 @@
Visitor, Visitor,
ensure_visible, ensure_visible,
is_arith_like, is_arith_like,
is_async_stmt_or_funcdef,
is_atom_with_invisible_parens, is_atom_with_invisible_parens,
is_docstring, is_docstring,
is_empty_tuple, is_empty_tuple,
@ -110,6 +111,17 @@ def line(self, indent: int = 0) -> Iterator[Line]:
self.current_line.depth += indent self.current_line.depth += indent
return # Line is empty, don't emit. Creating a new one unnecessary. return # Line is empty, don't emit. Creating a new one unnecessary.
if (
Preview.improved_async_statements_handling in self.mode
and len(self.current_line.leaves) == 1
and is_async_stmt_or_funcdef(self.current_line.leaves[0])
):
# Special case for async def/for/with statements. `visit_async_stmt`
# adds an `ASYNC` leaf then visits the child def/for/with statement
# nodes. Line yields from those nodes shouldn't treat the former
# `ASYNC` leaf as a complete line.
return
complete_line = self.current_line complete_line = self.current_line
self.current_line = Line(mode=self.mode, depth=complete_line.depth + indent) self.current_line = Line(mode=self.mode, depth=complete_line.depth + indent)
yield complete_line yield complete_line
@ -301,8 +313,11 @@ def visit_async_stmt(self, node: Node) -> Iterator[Line]:
break break
internal_stmt = next(children) internal_stmt = next(children)
for child in internal_stmt.children: if Preview.improved_async_statements_handling in self.mode:
yield from self.visit(child) yield from self.visit(internal_stmt)
else:
for child in internal_stmt.children:
yield from self.visit(child)
def visit_decorators(self, node: Node) -> Iterator[Line]: def visit_decorators(self, node: Node) -> Iterator[Line]:
"""Visit decorators.""" """Visit decorators."""

View File

@ -28,7 +28,7 @@
is_multiline_string, is_multiline_string,
is_one_sequence_between, is_one_sequence_between,
is_type_comment, is_type_comment,
is_with_stmt, is_with_or_async_with_stmt,
replace_child, replace_child,
syms, syms,
whitespace, whitespace,
@ -124,9 +124,9 @@ def is_import(self) -> bool:
return bool(self) and is_import(self.leaves[0]) return bool(self) and is_import(self.leaves[0])
@property @property
def is_with_stmt(self) -> bool: def is_with_or_async_with_stmt(self) -> bool:
"""Is this a with_stmt line?""" """Is this a with_stmt line?"""
return bool(self) and is_with_stmt(self.leaves[0]) return bool(self) and is_with_or_async_with_stmt(self.leaves[0])
@property @property
def is_class(self) -> bool: def is_class(self) -> bool:
@ -872,7 +872,7 @@ def can_omit_invisible_parens(
if ( if (
Preview.wrap_multiple_context_managers_in_parens in line.mode Preview.wrap_multiple_context_managers_in_parens in line.mode
and max_priority == COMMA_PRIORITY and max_priority == COMMA_PRIORITY
and rhs.head.is_with_stmt and rhs.head.is_with_or_async_with_stmt
): ):
# For two context manager with statements, the optional parentheses read # For two context manager with statements, the optional parentheses read
# better. In this case, `rhs.body` is the context managers part of # better. In this case, `rhs.body` is the context managers part of

View File

@ -155,6 +155,7 @@ class Preview(Enum):
add_trailing_comma_consistently = auto() add_trailing_comma_consistently = auto()
hex_codes_in_unicode_sequences = auto() hex_codes_in_unicode_sequences = auto()
improved_async_statements_handling = auto()
multiline_string_handling = auto() multiline_string_handling = auto()
prefer_splitting_right_hand_side_of_assignments = auto() prefer_splitting_right_hand_side_of_assignments = auto()
# NOTE: string_processing requires wrap_long_dict_values_in_parens # NOTE: string_processing requires wrap_long_dict_values_in_parens

View File

@ -789,13 +789,30 @@ def is_import(leaf: Leaf) -> bool:
) )
def is_with_stmt(leaf: Leaf) -> bool: def is_with_or_async_with_stmt(leaf: Leaf) -> bool:
"""Return True if the given leaf starts a with statement.""" """Return True if the given leaf starts a with or async with statement."""
return bool( return bool(
leaf.type == token.NAME leaf.type == token.NAME
and leaf.value == "with" and leaf.value == "with"
and leaf.parent and leaf.parent
and leaf.parent.type == syms.with_stmt and leaf.parent.type == syms.with_stmt
) or bool(
leaf.type == token.ASYNC
and leaf.next_sibling
and leaf.next_sibling.type == syms.with_stmt
)
def is_async_stmt_or_funcdef(leaf: Leaf) -> bool:
"""Return True if the given leaf starts an async def/for/with statement.
Note that `async def` can be either an `async_stmt` or `async_funcdef`,
the latter is used when it has decorators.
"""
return bool(
leaf.type == token.ASYNC
and leaf.parent
and leaf.parent.type in {syms.async_stmt, syms.async_funcdef}
) )

View File

@ -0,0 +1,27 @@
async def func() -> (int):
return 0
@decorated
async def func() -> (int):
return 0
async for (item) in async_iter:
pass
# output
async def func() -> int:
return 0
@decorated
async def func() -> int:
return 0
async for item in async_iter:
pass

View File

@ -67,6 +67,23 @@
pass pass
async def func():
async with \
make_context_manager1() as cm1, \
make_context_manager2() as cm2, \
make_context_manager3() as cm3, \
make_context_manager4() as cm4 \
:
pass
async with some_function(
argument1, argument2, argument3="some_value"
) as some_cm, some_other_function(
argument1, argument2, argument3="some_value"
):
pass
# output # output
@ -139,3 +156,19 @@
] ]
).another_method() as cmd: ).another_method() as cmd:
pass pass
async def func():
async with (
make_context_manager1() as cm1,
make_context_manager2() as cm2,
make_context_manager3() as cm3,
make_context_manager4() as cm4,
):
pass
async with (
some_function(argument1, argument2, argument3="some_value") as some_cm,
some_other_function(argument1, argument2, argument3="some_value"),
):
pass