Skip to content

Commit 459562c

Browse files
Pedro-Muller29JelleZijlstrahauntsaninja
authored
Improve function declaration wrapping when it contains generic type definitions (#4553)
--------- Co-authored-by: Jelle Zijlstra <[email protected]> Co-authored-by: hauntsaninja <[email protected]> Co-authored-by: Shantanu <[email protected]>
1 parent 99dbf30 commit 459562c

File tree

5 files changed

+507
-30
lines changed

5 files changed

+507
-30
lines changed

CHANGES.md

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ the following changes:
2222
The following changes were not in any previous release:
2323

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

2628
### Stable style
2729

src/black/linegen.py

+23-20
Original file line numberDiff line numberDiff line change
@@ -779,26 +779,29 @@ def left_hand_split(
779779
Prefer RHS otherwise. This is why this function is not symmetrical with
780780
:func:`right_hand_split` which also handles optional parentheses.
781781
"""
782-
tail_leaves: list[Leaf] = []
783-
body_leaves: list[Leaf] = []
784-
head_leaves: list[Leaf] = []
785-
current_leaves = head_leaves
786-
matching_bracket: Optional[Leaf] = None
787-
for leaf in line.leaves:
788-
if (
789-
current_leaves is body_leaves
790-
and leaf.type in CLOSING_BRACKETS
791-
and leaf.opening_bracket is matching_bracket
792-
and isinstance(matching_bracket, Leaf)
793-
):
794-
ensure_visible(leaf)
795-
ensure_visible(matching_bracket)
796-
current_leaves = tail_leaves if body_leaves else head_leaves
797-
current_leaves.append(leaf)
798-
if current_leaves is head_leaves:
799-
if leaf.type in OPENING_BRACKETS:
800-
matching_bracket = leaf
801-
current_leaves = body_leaves
782+
for leaf_type in [token.LPAR, token.LSQB]:
783+
tail_leaves: list[Leaf] = []
784+
body_leaves: list[Leaf] = []
785+
head_leaves: list[Leaf] = []
786+
current_leaves = head_leaves
787+
matching_bracket: Optional[Leaf] = None
788+
for leaf in line.leaves:
789+
if (
790+
current_leaves is body_leaves
791+
and leaf.type in CLOSING_BRACKETS
792+
and leaf.opening_bracket is matching_bracket
793+
and isinstance(matching_bracket, Leaf)
794+
):
795+
ensure_visible(leaf)
796+
ensure_visible(matching_bracket)
797+
current_leaves = tail_leaves if body_leaves else head_leaves
798+
current_leaves.append(leaf)
799+
if current_leaves is head_leaves:
800+
if leaf.type == leaf_type:
801+
matching_bracket = leaf
802+
current_leaves = body_leaves
803+
if matching_bracket and tail_leaves:
804+
break
802805
if not matching_bracket or not tail_leaves:
803806
raise CannotSplit("No brackets found")
804807

tests/data/cases/generics_wrapping.py

+307
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
# flags: --minimum-version=3.12
2+
def plain[T, B](a: T, b: T) -> T:
3+
return a
4+
5+
def arg_magic[T, B](a: T, b: T,) -> T:
6+
return a
7+
8+
def type_param_magic[T, B,](a: T, b: T) -> T:
9+
return a
10+
11+
def both_magic[T, B,](a: T, b: T,) -> T:
12+
return a
13+
14+
15+
def plain_multiline[
16+
T,
17+
B
18+
](
19+
a: T,
20+
b: T
21+
) -> T:
22+
return a
23+
24+
def arg_magic_multiline[
25+
T,
26+
B
27+
](
28+
a: T,
29+
b: T,
30+
) -> T:
31+
return a
32+
33+
def type_param_magic_multiline[
34+
T,
35+
B,
36+
](
37+
a: T,
38+
b: T
39+
) -> T:
40+
return a
41+
42+
def both_magic_multiline[
43+
T,
44+
B,
45+
](
46+
a: T,
47+
b: T,
48+
) -> T:
49+
return a
50+
51+
52+
def plain_mixed1[
53+
T,
54+
B
55+
](a: T, b: T) -> T:
56+
return a
57+
58+
def plain_mixed2[T, B](
59+
a: T,
60+
b: T
61+
) -> T:
62+
return a
63+
64+
def arg_magic_mixed1[
65+
T,
66+
B
67+
](a: T, b: T,) -> T:
68+
return a
69+
70+
def arg_magic_mixed2[T, B](
71+
a: T,
72+
b: T,
73+
) -> T:
74+
return a
75+
76+
def type_param_magic_mixed1[
77+
T,
78+
B,
79+
](a: T, b: T) -> T:
80+
return a
81+
82+
def type_param_magic_mixed2[T, B,](
83+
a: T,
84+
b: T
85+
) -> T:
86+
return a
87+
88+
def both_magic_mixed1[
89+
T,
90+
B,
91+
](a: T, b: T,) -> T:
92+
return a
93+
94+
def both_magic_mixed2[T, B,](
95+
a: T,
96+
b: T,
97+
) -> T:
98+
return a
99+
100+
def something_something_function[
101+
T: Model
102+
](param: list[int], other_param: type[T], *, some_other_param: bool = True) -> QuerySet[
103+
T
104+
]:
105+
pass
106+
107+
108+
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:
109+
return a
110+
111+
112+
def with_random_comments[
113+
Z
114+
# bye
115+
]():
116+
return a
117+
118+
119+
def func[
120+
T, # comment
121+
U # comment
122+
,
123+
Z: # comment
124+
int
125+
](): pass
126+
127+
128+
def func[
129+
T, # comment but it's long so it doesn't just move to the end of the line
130+
U # comment comment comm comm ent ent
131+
,
132+
Z: # comment ent ent comm comm comment
133+
int
134+
](): pass
135+
136+
137+
# output
138+
def plain[T, B](a: T, b: T) -> T:
139+
return a
140+
141+
142+
def arg_magic[T, B](
143+
a: T,
144+
b: T,
145+
) -> T:
146+
return a
147+
148+
149+
def type_param_magic[
150+
T,
151+
B,
152+
](
153+
a: T, b: T
154+
) -> T:
155+
return a
156+
157+
158+
def both_magic[
159+
T,
160+
B,
161+
](
162+
a: T,
163+
b: T,
164+
) -> T:
165+
return a
166+
167+
168+
def plain_multiline[T, B](a: T, b: T) -> T:
169+
return a
170+
171+
172+
def arg_magic_multiline[T, B](
173+
a: T,
174+
b: T,
175+
) -> T:
176+
return a
177+
178+
179+
def type_param_magic_multiline[
180+
T,
181+
B,
182+
](
183+
a: T, b: T
184+
) -> T:
185+
return a
186+
187+
188+
def both_magic_multiline[
189+
T,
190+
B,
191+
](
192+
a: T,
193+
b: T,
194+
) -> T:
195+
return a
196+
197+
198+
def plain_mixed1[T, B](a: T, b: T) -> T:
199+
return a
200+
201+
202+
def plain_mixed2[T, B](a: T, b: T) -> T:
203+
return a
204+
205+
206+
def arg_magic_mixed1[T, B](
207+
a: T,
208+
b: T,
209+
) -> T:
210+
return a
211+
212+
213+
def arg_magic_mixed2[T, B](
214+
a: T,
215+
b: T,
216+
) -> T:
217+
return a
218+
219+
220+
def type_param_magic_mixed1[
221+
T,
222+
B,
223+
](
224+
a: T, b: T
225+
) -> T:
226+
return a
227+
228+
229+
def type_param_magic_mixed2[
230+
T,
231+
B,
232+
](
233+
a: T, b: T
234+
) -> T:
235+
return a
236+
237+
238+
def both_magic_mixed1[
239+
T,
240+
B,
241+
](
242+
a: T,
243+
b: T,
244+
) -> T:
245+
return a
246+
247+
248+
def both_magic_mixed2[
249+
T,
250+
B,
251+
](
252+
a: T,
253+
b: T,
254+
) -> T:
255+
return a
256+
257+
258+
def something_something_function[T: Model](
259+
param: list[int], other_param: type[T], *, some_other_param: bool = True
260+
) -> QuerySet[T]:
261+
pass
262+
263+
264+
def func[
265+
A_LOT_OF_GENERIC_TYPES: AreBeingDefinedHere,
266+
LIKE_THIS,
267+
AND_THIS,
268+
ANOTHER_ONE,
269+
AND_YET_ANOTHER_ONE: ThisOneHasTyping,
270+
](
271+
a: T,
272+
b: T,
273+
c: T,
274+
d: T,
275+
e: T,
276+
f: T,
277+
g: T,
278+
h: T,
279+
i: T,
280+
j: T,
281+
k: T,
282+
l: T,
283+
m: T,
284+
n: T,
285+
o: T,
286+
p: T,
287+
) -> T:
288+
return a
289+
290+
291+
def with_random_comments[
292+
Z
293+
# bye
294+
]():
295+
return a
296+
297+
298+
def func[T, U, Z: int](): # comment # comment # comment
299+
pass
300+
301+
302+
def func[
303+
T, # comment but it's long so it doesn't just move to the end of the line
304+
U, # comment comment comm comm ent ent
305+
Z: int, # comment ent ent comm comm comment
306+
]():
307+
pass

0 commit comments

Comments
 (0)