Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve function declaration wrapping when it contains generic type definitions #4553

Merged
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ the following changes:
The following changes were not in any previous release:

- Remove parentheses around sole list items (#4312)
- Generic function definitions are now formatted more elegantly: parameters are
split over multiple lines first instead of type parameter definitions (#4553)

### Stable style

Expand Down
43 changes: 23 additions & 20 deletions src/black/linegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -779,26 +779,29 @@ def left_hand_split(
Prefer RHS otherwise. This is why this function is not symmetrical with
:func:`right_hand_split` which also handles optional parentheses.
"""
tail_leaves: list[Leaf] = []
body_leaves: list[Leaf] = []
head_leaves: list[Leaf] = []
current_leaves = head_leaves
matching_bracket: Optional[Leaf] = None
for leaf in line.leaves:
if (
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:
if leaf.type in OPENING_BRACKETS:
matching_bracket = leaf
current_leaves = body_leaves
for leaf_type in [token.LPAR, token.LSQB]:
tail_leaves: list[Leaf] = []
body_leaves: list[Leaf] = []
head_leaves: list[Leaf] = []
current_leaves = head_leaves
matching_bracket: Optional[Leaf] = None
for leaf in line.leaves:
if (
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:
if leaf.type == leaf_type:
matching_bracket = leaf
current_leaves = body_leaves
if matching_bracket and tail_leaves:
break
if not matching_bracket or not tail_leaves:
raise CannotSplit("No brackets found")

Expand Down
307 changes: 307 additions & 0 deletions tests/data/cases/generics_wrapping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
# flags: --minimum-version=3.12
def plain[T, B](a: T, b: T) -> T:
return a

def arg_magic[T, B](a: T, b: T,) -> T:
return a

def type_param_magic[T, B,](a: T, b: T) -> T:
return a

def both_magic[T, B,](a: T, b: T,) -> T:
return a


def plain_multiline[
T,
B
](
a: T,
b: T
) -> T:
return a

def arg_magic_multiline[
T,
B
](
a: T,
b: T,
) -> T:
return a

def type_param_magic_multiline[
T,
B,
](
a: T,
b: T
) -> T:
return a

def both_magic_multiline[
T,
B,
](
a: T,
b: T,
) -> T:
return a


def plain_mixed1[
T,
B
](a: T, b: T) -> T:
return a

def plain_mixed2[T, B](
a: T,
b: T
) -> T:
return a

def arg_magic_mixed1[
T,
B
](a: T, b: T,) -> T:
return a

def arg_magic_mixed2[T, B](
a: T,
b: T,
) -> T:
return a

def type_param_magic_mixed1[
T,
B,
](a: T, b: T) -> T:
return a

def type_param_magic_mixed2[T, B,](
a: T,
b: T
) -> T:
return a

def both_magic_mixed1[
T,
B,
](a: T, b: T,) -> T:
return a

def both_magic_mixed2[T, B,](
a: T,
b: T,
) -> T:
return a

def something_something_function[
T: Model
](param: list[int], other_param: type[T], *, some_other_param: bool = True) -> QuerySet[
T
]:
pass


def func[A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere, LIKE_THIS, AND_THIS, ANOTHER_ONE, AND_YET_ANOTHER_ONE: ThisOneHasTyping](a: T, b: T, c: T, d: T, e: T, f: T, g: T, h: T, i: T, j: T, k: T, l: T, m: T, n: T, o: T, p: T) -> T:
return a


def with_random_comments[
Z
# bye
]():
return a


def func[
T, # comment
U # comment
,
Z: # comment
int
](): pass


def func[
T, # comment but it's long so it doesn't just move to the end of the line
U # comment comment comm comm ent ent
,
Z: # comment ent ent comm comm comment
int
](): pass


# output
def plain[T, B](a: T, b: T) -> T:
return a


def arg_magic[T, B](
a: T,
b: T,
) -> T:
return a


def type_param_magic[
T,
B,
](
a: T, b: T
) -> T:
return a


def both_magic[
T,
B,
](
a: T,
b: T,
) -> T:
return a


def plain_multiline[T, B](a: T, b: T) -> T:
return a


def arg_magic_multiline[T, B](
a: T,
b: T,
) -> T:
return a


def type_param_magic_multiline[
T,
B,
](
a: T, b: T
) -> T:
return a


def both_magic_multiline[
T,
B,
](
a: T,
b: T,
) -> T:
return a


def plain_mixed1[T, B](a: T, b: T) -> T:
return a


def plain_mixed2[T, B](a: T, b: T) -> T:
return a


def arg_magic_mixed1[T, B](
a: T,
b: T,
) -> T:
return a


def arg_magic_mixed2[T, B](
a: T,
b: T,
) -> T:
return a


def type_param_magic_mixed1[
T,
B,
](
a: T, b: T
) -> T:
return a


def type_param_magic_mixed2[
T,
B,
](
a: T, b: T
) -> T:
return a


def both_magic_mixed1[
T,
B,
](
a: T,
b: T,
) -> T:
return a


def both_magic_mixed2[
T,
B,
](
a: T,
b: T,
) -> T:
return a


def something_something_function[T: Model](
param: list[int], other_param: type[T], *, some_other_param: bool = True
) -> QuerySet[T]:
pass


def func[
A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere,
LIKE_THIS,
AND_THIS,
ANOTHER_ONE,
AND_YET_ANOTHER_ONE: ThisOneHasTyping,
](
a: T,
b: T,
c: T,
d: T,
e: T,
f: T,
g: T,
h: T,
i: T,
j: T,
k: T,
l: T,
m: T,
n: T,
o: T,
p: T,
) -> T:
return a


def with_random_comments[
Z
# bye
]():
return a


def func[T, U, Z: int](): # comment # comment # comment
pass


def func[
T, # comment but it's long so it doesn't just move to the end of the line
U, # comment comment comm comm ent ent
Z: int, # comment ent ent comm comm comment
]():
pass
Loading
Loading