Remove redundant parentheses around awaited coroutines/tasks (#2991)
This is a tricky one as await is technically an expression and therefore in certain situations requires brackets for operator precedence. However, the vast majority of await usage is just await some_coroutine(...) and similar in format to return statements. Therefore this PR removes redundant parens around these await expressions. Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> Co-authored-by: Richard Si <63936253+ichard26@users.noreply.github.com>
This commit is contained in:
parent
98fcccee55
commit
75f99bded3
@ -14,6 +14,7 @@
|
||||
|
||||
<!-- Changes that affect Black's preview style -->
|
||||
|
||||
- Remove redundant parentheses around awaited objects (#2991)
|
||||
- Parentheses around return annotations are now managed (#2990)
|
||||
- Remove unnecessary parentheses from `with` statements (#2926)
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
"""
|
||||
from functools import partial, wraps
|
||||
import sys
|
||||
from typing import Collection, Iterator, List, Optional, Set, Union
|
||||
from typing import Collection, Iterator, List, Optional, Set, Union, cast
|
||||
|
||||
from black.nodes import WHITESPACE, RARROW, STATEMENT, STANDALONE_COMMENT
|
||||
from black.nodes import ASSIGNMENTS, OPENING_BRACKETS, CLOSING_BRACKETS
|
||||
@ -253,6 +253,9 @@ def visit_power(self, node: Node) -> Iterator[Line]:
|
||||
):
|
||||
wrap_in_parentheses(node, leaf)
|
||||
|
||||
if Preview.remove_redundant_parens in self.mode:
|
||||
remove_await_parens(node)
|
||||
|
||||
yield from self.visit_default(node)
|
||||
|
||||
def visit_SEMI(self, leaf: Leaf) -> Iterator[Line]:
|
||||
@ -923,6 +926,42 @@ def normalize_invisible_parens(
|
||||
)
|
||||
|
||||
|
||||
def remove_await_parens(node: Node) -> None:
|
||||
if node.children[0].type == token.AWAIT and len(node.children) > 1:
|
||||
if (
|
||||
node.children[1].type == syms.atom
|
||||
and node.children[1].children[0].type == token.LPAR
|
||||
):
|
||||
if maybe_make_parens_invisible_in_atom(
|
||||
node.children[1],
|
||||
parent=node,
|
||||
remove_brackets_around_comma=True,
|
||||
):
|
||||
wrap_in_parentheses(node, node.children[1], visible=False)
|
||||
|
||||
# Since await is an expression we shouldn't remove
|
||||
# brackets in cases where this would change
|
||||
# the AST due to operator precedence.
|
||||
# Therefore we only aim to remove brackets around
|
||||
# power nodes that aren't also await expressions themselves.
|
||||
# https://peps.python.org/pep-0492/#updated-operator-precedence-table
|
||||
# N.B. We've still removed any redundant nested brackets though :)
|
||||
opening_bracket = cast(Leaf, node.children[1].children[0])
|
||||
closing_bracket = cast(Leaf, node.children[1].children[-1])
|
||||
bracket_contents = cast(Node, node.children[1].children[1])
|
||||
if bracket_contents.type != syms.power:
|
||||
ensure_visible(opening_bracket)
|
||||
ensure_visible(closing_bracket)
|
||||
elif (
|
||||
bracket_contents.type == syms.power
|
||||
and bracket_contents.children[0].type == token.AWAIT
|
||||
):
|
||||
ensure_visible(opening_bracket)
|
||||
ensure_visible(closing_bracket)
|
||||
# If we are in a nested await then recurse down.
|
||||
remove_await_parens(bracket_contents)
|
||||
|
||||
|
||||
def remove_with_parens(node: Node, parent: Node) -> None:
|
||||
"""Recursively hide optional parens in `with` statements."""
|
||||
# Removing all unnecessary parentheses in with statements in one pass is a tad
|
||||
|
168
tests/data/remove_await_parens.py
Normal file
168
tests/data/remove_await_parens.py
Normal file
@ -0,0 +1,168 @@
|
||||
import asyncio
|
||||
|
||||
# Control example
|
||||
async def main():
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Remove brackets for short coroutine/task
|
||||
async def main():
|
||||
await (asyncio.sleep(1))
|
||||
|
||||
async def main():
|
||||
await (
|
||||
asyncio.sleep(1)
|
||||
)
|
||||
|
||||
async def main():
|
||||
await (asyncio.sleep(1)
|
||||
)
|
||||
|
||||
# Check comments
|
||||
async def main():
|
||||
await ( # Hello
|
||||
asyncio.sleep(1)
|
||||
)
|
||||
|
||||
async def main():
|
||||
await (
|
||||
asyncio.sleep(1) # Hello
|
||||
)
|
||||
|
||||
async def main():
|
||||
await (
|
||||
asyncio.sleep(1)
|
||||
) # Hello
|
||||
|
||||
# Long lines
|
||||
async def main():
|
||||
await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1))
|
||||
|
||||
# Same as above but with magic trailing comma in function
|
||||
async def main():
|
||||
await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1),)
|
||||
|
||||
# Cr@zY Br@ck3Tz
|
||||
async def main():
|
||||
await (
|
||||
(((((((((((((
|
||||
((( (((
|
||||
((( (((
|
||||
((( (((
|
||||
((( (((
|
||||
((black(1)))
|
||||
))) )))
|
||||
))) )))
|
||||
))) )))
|
||||
))) )))
|
||||
)))))))))))))
|
||||
)
|
||||
|
||||
# Keep brackets around non power operations and nested awaits
|
||||
async def main():
|
||||
await (set_of_tasks | other_set)
|
||||
|
||||
async def main():
|
||||
await (await asyncio.sleep(1))
|
||||
|
||||
# It's awaits all the way down...
|
||||
async def main():
|
||||
await (await x)
|
||||
|
||||
async def main():
|
||||
await (yield x)
|
||||
|
||||
async def main():
|
||||
await (await (asyncio.sleep(1)))
|
||||
|
||||
async def main():
|
||||
await (await (await (await (await (asyncio.sleep(1))))))
|
||||
|
||||
# output
|
||||
import asyncio
|
||||
|
||||
# Control example
|
||||
async def main():
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
# Remove brackets for short coroutine/task
|
||||
async def main():
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
async def main():
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
async def main():
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
# Check comments
|
||||
async def main():
|
||||
await asyncio.sleep(1) # Hello
|
||||
|
||||
|
||||
async def main():
|
||||
await asyncio.sleep(1) # Hello
|
||||
|
||||
|
||||
async def main():
|
||||
await asyncio.sleep(1) # Hello
|
||||
|
||||
|
||||
# Long lines
|
||||
async def main():
|
||||
await asyncio.gather(
|
||||
asyncio.sleep(1),
|
||||
asyncio.sleep(1),
|
||||
asyncio.sleep(1),
|
||||
asyncio.sleep(1),
|
||||
asyncio.sleep(1),
|
||||
asyncio.sleep(1),
|
||||
asyncio.sleep(1),
|
||||
)
|
||||
|
||||
|
||||
# Same as above but with magic trailing comma in function
|
||||
async def main():
|
||||
await asyncio.gather(
|
||||
asyncio.sleep(1),
|
||||
asyncio.sleep(1),
|
||||
asyncio.sleep(1),
|
||||
asyncio.sleep(1),
|
||||
asyncio.sleep(1),
|
||||
asyncio.sleep(1),
|
||||
asyncio.sleep(1),
|
||||
)
|
||||
|
||||
|
||||
# Cr@zY Br@ck3Tz
|
||||
async def main():
|
||||
await black(1)
|
||||
|
||||
|
||||
# Keep brackets around non power operations and nested awaits
|
||||
async def main():
|
||||
await (set_of_tasks | other_set)
|
||||
|
||||
|
||||
async def main():
|
||||
await (await asyncio.sleep(1))
|
||||
|
||||
|
||||
# It's awaits all the way down...
|
||||
async def main():
|
||||
await (await x)
|
||||
|
||||
|
||||
async def main():
|
||||
await (yield x)
|
||||
|
||||
|
||||
async def main():
|
||||
await (await asyncio.sleep(1))
|
||||
|
||||
|
||||
async def main():
|
||||
await (await (await (await (await asyncio.sleep(1)))))
|
@ -83,6 +83,7 @@
|
||||
"remove_except_parens",
|
||||
"remove_for_brackets",
|
||||
"one_element_subscript",
|
||||
"remove_await_parens",
|
||||
"return_annotation_brackets",
|
||||
]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user