Skip to content

Commit

Permalink
fix indentation of line breaks in long type hints by adding parenthes…
Browse files Browse the repository at this point in the history
…es, and remove unnecessary parentheses
  • Loading branch information
jakkdl committed Sep 20, 2023
1 parent e974fc3 commit 0f3cf1d
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 3 deletions.
20 changes: 19 additions & 1 deletion src/black/linegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,23 @@ def visit_factor(self, node: Node) -> Iterator[Line]:
node.insert_child(index, Node(syms.atom, [lpar, operand, rpar]))
yield from self.visit_default(node)

def visit_tname(self, node: Node) -> Iterator[Line]:
"""
Add potential parentheses around types in function parameter lists to be made
into real parentheses in case the type hint is too long to fit on a line
Examples:
def foo(a: int, b: float = 7): ...
->
def foo(a: (int), b: (float) = 7): ...
"""
assert len(node.children) == 3
if maybe_make_parens_invisible_in_atom(node.children[2], parent=node):
wrap_in_parentheses(node, node.children[2], visible=False)

yield from self.visit_default(node)

def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
if Preview.hex_codes_in_unicode_sequences in self.mode:
normalize_unicode_escape_sequences(leaf)
Expand Down Expand Up @@ -1368,7 +1385,7 @@ def maybe_make_parens_invisible_in_atom(
Returns whether the node should itself be wrapped in invisible parentheses.
"""
if (
node.type != syms.atom
node.type not in (syms.atom, syms.expr)
or is_empty_tuple(node)
or is_one_tuple(node)
or (is_yield(node) and parent.type != syms.expr_stmt)
Expand All @@ -1392,6 +1409,7 @@ def maybe_make_parens_invisible_in_atom(
syms.except_clause,
syms.funcdef,
syms.with_stmt,
syms.tname,
# these ones aren't useful to end users, but they do please fuzzers
syms.for_stmt,
syms.del_stmt,
Expand Down
2 changes: 2 additions & 0 deletions src/black/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@
">>=",
"**=",
"//=",
# also handle annassign
":",
}

IMPLICIT_TUPLE: Final = {syms.testlist, syms.testlist_star_expr, syms.exprlist}
Expand Down
4 changes: 3 additions & 1 deletion tests/data/miscellaneous/long_strings_flag_disabled.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,9 @@
+ CONCATENATED
+ "using the '+' operator."
)
annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
annotated_variable: (
Final
) = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
annotated_variable: Literal[
"fakse_literal"
] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
Expand Down
2 changes: 1 addition & 1 deletion tests/data/preview/long_strings__type_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ def func(


def func(
argument: ("int |" "str"),
argument: "int |" "str",
) -> Set["int |" " str"]:
pass
190 changes: 190 additions & 0 deletions tests/data/py_310/pep604_union_types_line_breaks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# This has always worked
z= Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong

# "AnnAssign"s now also work
z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong
z: (Short
| Short2
| Short3
| Short4)
z: (int)
z: ((int))


z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong = 7
z: (Short
| Short2
| Short3
| Short4) = 8
z: (int) = 2.3
z: ((int)) = foo()

# In case I go for not enforcing parantheses, this might get improved at the same time
x = (
z
== 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999,
y
== 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999,
)

x = (
z == (9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999),
y == (9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999),
)

# handle formatting of "tname"s in parameter list

# remove unnecessary paren
def foo(i: (int)) -> None: ...


# this is a syntax error in the type annotation according to mypy, but it's not invalid *python* code, so make sure we don't mess with it and make it so.
def foo(i: (int,)) -> None: ...

def foo(
i: int,
x: Loooooooooooooooooooooooong
| Looooooooooooooooong
| Looooooooooooooooooooong
| Looooooong,
*,
s: str,
) -> None:
pass


@app.get("/path/")
async def foo(
q: str
| None = Query(None, title="Some long title", description="Some long description")
):
pass


def f(
max_jobs: int
| None = Option(
None, help="Maximum number of jobs to launch. And some additional text."
),
another_option: bool = False
):
...


# output
# This has always worked
z = (
Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
)

# "AnnAssign"s now also work
z: (
Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
)
z: Short | Short2 | Short3 | Short4
z: int
z: int


z: (
Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
| Loooooooooooooooooooooooong
) = 7
z: Short | Short2 | Short3 | Short4 = 8
z: int = 2.3
z: int = foo()

# In case I go for not enforcing parantheses, this might get improved at the same time
x = (
z
== 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999,
y
== 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999,
)

x = (
z
== (
9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
| 9999999999999999999999999999999999999999
),
y
== (
9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
+ 9999999999999999999999999999999999999999
),
)

# handle formatting of "tname"s in parameter list


# remove unnecessary paren
def foo(i: int) -> None:
...


# this is a syntax error in the type annotation according to mypy, but it's not invalid *python* code, so make sure we don't mess with it and make it so.
def foo(i: (int,)) -> None:
...


def foo(
i: int,
x: (
Loooooooooooooooooooooooong
| Looooooooooooooooong
| Looooooooooooooooooooong
| Looooooong
),
*,
s: str,
) -> None:
pass


@app.get("/path/")
async def foo(
q: str | None = Query(
None, title="Some long title", description="Some long description"
)
):
pass


def f(
max_jobs: int | None = Option(
None, help="Maximum number of jobs to launch. And some additional text."
),
another_option: bool = False,
):
...

0 comments on commit 0f3cf1d

Please sign in to comment.