Repository: psf/black
Pull request: Remove redundant parentheses around awaited coroutines/tasks
### Description
Closes #275. Remove redundant parentheses around awaited coroutines/tasks.

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.

Let's see what diff shades shades has to say...

### Checklist - did you ...

- [X] Add a CHANGELOG entry if necessary?
- [X] Add / update tests if necessary?
- [X] Add new / update outdated documentation?


Problem statement:
Should `await` automatically manage parentheses like `return`?
On a first glance, it would be better. On another though, `await` is an expression which can potentially complicate formattings that are now rather simple.

Alternatively, `await` should be ignored by an existing bracket pair for purposes of splitting on delimiters, enabling for example fluent interfaces.

Hints from issue discussion:
Concrete example:
```
% black -c 'return (a)'   
return a
% black -c 'await (a)' 
await (a)
```

Commit message: await brackets

Patch to review:
diff --git a/CHANGES.md b/CHANGES.md
index 30c00566b3c..449c9fb6d85 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -14,6 +14,7 @@
 
 <!-- Changes that affect Black's preview style -->
 
+- Parentheses around awaited coroutines/tasks are now managed (#2991)
 - Remove unnecessary parentheses from `with` statements (#2926)
 
 ### _Blackd_
diff --git a/src/black/linegen.py b/src/black/linegen.py
index 2cf9cf3130a..1e25da3bd70 100644
--- a/src/black/linegen.py
+++ b/src/black/linegen.py
@@ -226,6 +226,37 @@ def visit_power(self, node: Node) -> Iterator[Line]:
             ):
                 wrap_in_parentheses(node, leaf)
 
+        if (
+            Preview.remove_redundant_parens in self.mode
+            and 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)
+                if (
+                    node.children[1].children[1].type != syms.power
+                    and isinstance(node.children[1].children[0], Leaf)
+                    and isinstance(node.children[1].children[-1], Leaf)
+                ):
+                    # Since await is an expression
+                    # it is syntactically possible
+                    # that someone would write `await (1 % 2)`
+                    # (although realistically unlikely given that it's a runtime error).
+                    # If we don't encounter a power being wrapped then we
+                    # should not remove all brackets due to operator precedence.
+                    # https://peps.python.org/pep-0492/#updated-operator-precedence-table
+                    # N.B. We've still removed any redundant nested brackets though :)
+                    ensure_visible(node.children[1].children[0])
+                    ensure_visible(node.children[1].children[-1])
+
         yield from self.visit_default(node)
 
     def visit_SEMI(self, leaf: Leaf) -> Iterator[Line]:
diff --git a/tests/data/remove_await_parens.py b/tests/data/remove_await_parens.py
new file mode 100644
index 00000000000..a5cf09b4c36
--- /dev/null
+++ b/tests/data/remove_await_parens.py
@@ -0,0 +1,122 @@
+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)))
+        )))        )))
+        )))         )))
+        )))         )))
+        )))        )))
+        )))))))))))))
+    )
+
+# 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)
diff --git a/tests/test_format.py b/tests/test_format.py
index d80eaa730cd..b998a71a542 100644
--- a/tests/test_format.py
+++ b/tests/test_format.py
@@ -83,6 +83,7 @@
     "remove_except_parens",
     "remove_for_brackets",
     "one_element_subscript",
+    "remove_await_parens",
 ]
 
 SOURCES: List[str] = [


Code review comments:
diff_hunk: @@ -14,6 +14,7 @@
 
 <!-- Changes that affect Black's preview style -->
 
+- Parentheses around awaited coroutines/tasks are now managed (#2991)
path: CHANGES.md
text: @user1:
Can we just say this?
```suggestion
- Remove redundant parentheses around awaited objects (#2991)
```
I suggested the "managed" wording on another PR because that one could also add parens, but here I think we only remove them. If we don't, you should add a test case where we add parens.

@author:
Ah good spot! I originally intended to add brackets as well before I realised how complicated handling the expressions would be and then took them out but forgot to update this 😄