Repository: psf/black

Issue:
Redundant parentheses around return annotation
```python
def infer_encoding_trajectory(encoding: EncodingElement,
                              observation: PoolingMessage,
                              prediction: PredictionMessage,
                              weights: hk.Params,
                              rng: Generator,
                              iterations: int) -> (
                                  tuple[EncodingState, EncodingTrajectory]):
```
became
```python
def infer_encoding_trajectory(
    encoding: EncodingElement,
    observation: PoolingMessage,
    prediction: PredictionMessage,
    weights: hk.Params,
    rng: Generator,
    iterations: int,
) -> (tuple[EncodingState, EncodingTrajectory]):
```
The parentheses were only there to allow wrapping and should have been removed.  In general, black doesn't need to preserve parentheses around an entire expression, including a type annotation.
Line too long: String in return annotation
**To Reproduce**

Given input:
```python
class ThisIsTrulyUnreasonablyExtremelyLongClassName:
    pass


def frobnicate() -> "ThisIsTrulyUnreasonablyExtremelyLongClassName | list[ThisIsTrulyUnreasonablyExtremelyLongClassName]":
    pass
```

Black does not split the long type hint of the function return type

Even if I explicitly attempt to split the string:
```python
def frobnicate() -> "ThisIsTrulyUnreasonablyExtremelyLongClassName | " \
                    "list[ThisIsTrulyUnreasonablyExtremelyLongClassName]":
    pass
```

then Black collapses it back together :(
```python
def frobnicate() -> "ThisIsTrulyUnreasonablyExtremelyLongClassName | " "list[ThisIsTrulyUnreasonablyExtremelyLongClassName]":
    pass
```

**Workaround**

Only by parenthesizing the type hint I can convince Black to keep it reasonable.

```python
def frobnicate() -> (
    "ThisIsTrulyUnreasonablyExtremelyLongClassName | "
    "list[ThisIsTrulyUnreasonablyExtremelyLongClassName]"
):
    pass
```

**Environment**

- Black's version: 21.12b0
- OS and Python version: macOS, Python 3.10.1



Discussion:
I agree, a PR would be welcome. In general, there's a number of places where Black should remove parentheses that it currently keeps. From past experience it's tricky sometimes to remove parentheses without causing stability bugs, but maybe in this context it won't be too bad.
Can confirm this exists on main as of commit e9f520c16abc8a864b61ae658bc3c91fda46fdd0 even with experimental string processing enabled. Thank you for the report!

Resolving patch:
diff --git a/src/black/linegen.py b/src/black/linegen.py
--- a/src/black/linegen.py
+++ b/src/black/linegen.py
@@ -144,6 +144,33 @@ def visit_stmt(
 
             yield from self.visit(child)
 
+    def visit_funcdef(self, node: Node) -> Iterator[Line]:
+        """Visit function definition."""
+        if Preview.annotation_parens not in self.mode:
+            yield from self.visit_stmt(node, keywords={"def"}, parens=set())
+        else:
+            yield from self.line()
+
+            # Remove redundant brackets around return type annotation.
+            is_return_annotation = False
+            for child in node.children:
+                if child.type == token.RARROW:
+                    is_return_annotation = True
+                elif is_return_annotation:
+                    if child.type == syms.atom and child.children[0].type == token.LPAR:
+                        if maybe_make_parens_invisible_in_atom(
+                            child,
+                            parent=node,
+                            remove_brackets_around_comma=False,
+                        ):
+                            wrap_in_parentheses(node, child, visible=False)
+                    else:
+                        wrap_in_parentheses(node, child, visible=False)
+                    is_return_annotation = False
+
+            for child in node.children:
+                yield from self.visit(child)
+
     def visit_match_case(self, node: Node) -> Iterator[Line]:
         """Visit either a match or case statement."""
         normalize_invisible_parens(node, parens_after=set(), preview=self.mode.preview)
@@ -326,7 +353,6 @@ def __post_init__(self) -> None:
         else:
             self.visit_except_clause = partial(v, keywords={"except"}, parens=Ø)
             self.visit_with_stmt = partial(v, keywords={"with"}, parens=Ø)
-        self.visit_funcdef = partial(v, keywords={"def"}, parens=Ø)
         self.visit_classdef = partial(v, keywords={"class"}, parens=Ø)
         self.visit_expr_stmt = partial(v, keywords=Ø, parens=ASSIGNMENTS)
         self.visit_return_stmt = partial(v, keywords={"return"}, parens={"return"})
@@ -478,7 +504,10 @@ def left_hand_split(line: Line, _features: Collection[Feature] = ()) -> Iterator
             current_leaves is body_leaves
             and leaf.type in CLOSING_BRACKETS
             and leaf.opening_bracket is matching_bracket
+            and isinstance(matching_bracket, Leaf)
         ):
+            ensure_visible(leaf)
+            ensure_visible(matching_bracket)
             current_leaves = tail_leaves if body_leaves else head_leaves
         current_leaves.append(leaf)
         if current_leaves is head_leaves:
diff --git a/src/black/mode.py b/src/black/mode.py
--- a/src/black/mode.py
+++ b/src/black/mode.py
@@ -129,6 +129,7 @@ class Preview(Enum):
     string_processing = auto()
     remove_redundant_parens = auto()
     one_element_subscript = auto()
+    annotation_parens = auto()
 
 
 class Deprecated(UserWarning):


Test patch:
diff --git a/tests/data/return_annotation_brackets.py b/tests/data/return_annotation_brackets.py
new file mode 100644
--- /dev/null
+++ b/tests/data/return_annotation_brackets.py
@@ -0,0 +1,222 @@
+# Control
+def double(a: int) -> int:
+    return 2*a
+
+# Remove the brackets
+def double(a: int) -> (int):
+    return 2*a
+
+# Some newline variations
+def double(a: int) -> (
+    int):
+    return 2*a
+
+def double(a: int) -> (int
+):
+    return 2*a
+
+def double(a: int) -> (
+    int
+):
+    return 2*a
+
+# Don't lose the comments
+def double(a: int) -> ( # Hello
+    int
+):
+    return 2*a
+
+def double(a: int) -> (
+    int # Hello
+):
+    return 2*a
+
+# Really long annotations
+def foo() -> (
+    intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
+):
+    return 2
+
+def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds:
+    return 2
+
+def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds:
+    return 2
+
+def foo(a: int, b: int, c: int,) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds:
+    return 2
+
+def foo(a: int, b: int, c: int,) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds:
+    return 2
+
+# Split args but no need to split return
+def foo(a: int, b: int, c: int,) -> int:
+    return 2
+
+# Deeply nested brackets
+# with *interesting* spacing
+def double(a: int) -> (((((int))))):
+    return 2*a
+
+def double(a: int) -> (
+    (  (
+        ((int)
+         )
+           )
+            )
+        ):
+    return 2*a
+
+def foo() -> (
+    (  (
+    intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
+)
+)):
+    return 2
+
+# Return type with commas
+def foo() -> (
+    tuple[int, int, int]
+):
+    return 2
+
+def foo() -> tuple[loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong]:
+    return 2
+
+# Magic trailing comma example
+def foo() -> tuple[int, int, int,]:
+    return 2
+
+# Long string example
+def frobnicate() -> "ThisIsTrulyUnreasonablyExtremelyLongClassName | list[ThisIsTrulyUnreasonablyExtremelyLongClassName]":
+    pass
+
+# output
+# Control
+def double(a: int) -> int:
+    return 2 * a
+
+
+# Remove the brackets
+def double(a: int) -> int:
+    return 2 * a
+
+
+# Some newline variations
+def double(a: int) -> int:
+    return 2 * a
+
+
+def double(a: int) -> int:
+    return 2 * a
+
+
+def double(a: int) -> int:
+    return 2 * a
+
+
+# Don't lose the comments
+def double(a: int) -> int:  # Hello
+    return 2 * a
+
+
+def double(a: int) -> int:  # Hello
+    return 2 * a
+
+
+# Really long annotations
+def foo() -> (
+    intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
+):
+    return 2
+
+
+def foo() -> (
+    intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
+):
+    return 2
+
+
+def foo() -> (
+    intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
+    | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
+):
+    return 2
+
+
+def foo(
+    a: int,
+    b: int,
+    c: int,
+) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds:
+    return 2
+
+
+def foo(
+    a: int,
+    b: int,
+    c: int,
+) -> (
+    intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
+    | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
+):
+    return 2
+
+
+# Split args but no need to split return
+def foo(
+    a: int,
+    b: int,
+    c: int,
+) -> int:
+    return 2
+
+
+# Deeply nested brackets
+# with *interesting* spacing
+def double(a: int) -> int:
+    return 2 * a
+
+
+def double(a: int) -> int:
+    return 2 * a
+
+
+def foo() -> (
+    intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds
+):
+    return 2
+
+
+# Return type with commas
+def foo() -> tuple[int, int, int]:
+    return 2
+
+
+def foo() -> (
+    tuple[
+        loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+        loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+        loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+    ]
+):
+    return 2
+
+
+# Magic trailing comma example
+def foo() -> (
+    tuple[
+        int,
+        int,
+        int,
+    ]
+):
+    return 2
+
+
+# Long string example
+def frobnicate() -> (
+    "ThisIsTrulyUnreasonablyExtremelyLongClassName |"
+    " list[ThisIsTrulyUnreasonablyExtremelyLongClassName]"
+):
+    pass
diff --git a/tests/test_format.py b/tests/test_format.py
--- a/tests/test_format.py
+++ b/tests/test_format.py
@@ -83,6 +83,7 @@
     "remove_except_parens",
     "remove_for_brackets",
     "one_element_subscript",
+    "return_annotation_brackets",
 ]
 
 SOURCES: List[str] = [