Skip to content

Commit 4f6ad7c

Browse files
Wrap the in clause of comprehensions across lines if necessary (#4699)
* CI: Remove now-uneeded workarounds The issues mentioned in the comments have all been fixed Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> * Fix Click typing issue in run self * oops, run self * i swear this was failing earlier ig it's not that related to this pr anyway * has click updated itself yet on pre-commit.ci * Bump click Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> * Split the `in` clause of comprehensions onto its own line if necessary Fixes #3498 Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> * Lint issues Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> * Move to preview style Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> * run self Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> * Alternative approach Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> * oops! Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> * run test on preview, not unstable Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> * Don't remove parens around ternaries Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update src/black/linegen.py * Update docs/the_black_code_style/future_style.md --------- Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 24f5169 commit 4f6ad7c

File tree

6 files changed

+185
-3
lines changed

6 files changed

+185
-3
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
- Improve `multiline_string_handling` with ternaries and dictionaries (#4657)
3535
- Fix a bug where `string_processing` would not split f-strings directly after
3636
expressions (#4680)
37+
- Wrap the `in` clause of comprehensions across lines if necessary (#4699)
3738
- Remove parentheses around multiple exception types in `except` and `except*` without
3839
`as`. (#4720)
3940

docs/the_black_code_style/future_style.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ Currently, the following features are included in the preview style:
2727
- `wrap_long_dict_values_in_parens`: Add parentheses around long values in dictionaries
2828
([see below](labels/wrap-long-dict-values))
2929
- `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behaviour on one-liner declarations,
30-
such as `def foo(): return "mock" # fmt: skip`, where previously the declaration
31-
would have been incorrectly collapsed.
30+
such as `def foo(): return "mock" # fmt: skip`, where previously the declaration would
31+
have been incorrectly collapsed.
32+
- `wrap_comprehension_in`: Wrap the `in` clause of list and dictionary comprehensions
33+
across lines if it would otherwise exceed the maximum line length.
3234
- `remove_parens_around_except_types`: Remove parentheses around multiple exception
3335
types in `except` and `except*` without `as`. See PEP 758 for details.
3436

src/black/linegen.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,16 @@ def visit_fstring(self, node: Node) -> Iterator[Line]:
579579

580580
# yield from self.visit_default(node)
581581

582+
def visit_comp_for(self, node: Node) -> Iterator[Line]:
583+
if Preview.wrap_comprehension_in in self.mode:
584+
normalize_invisible_parens(
585+
node, parens_after={"in"}, mode=self.mode, features=self.features
586+
)
587+
yield from self.visit_default(node)
588+
589+
def visit_old_comp_for(self, node: Node) -> Iterator[Line]:
590+
yield from self.visit_comp_for(node)
591+
582592
def __post_init__(self) -> None:
583593
"""You are in a twisty little maze of passages."""
584594
self.current_line = Line(mode=self.mode)
@@ -1466,7 +1476,13 @@ def normalize_invisible_parens( # noqa: C901
14661476
wrap_in_parentheses(node, child, visible=False)
14671477
elif isinstance(child, Node) and node.type == syms.with_stmt:
14681478
remove_with_parens(child, node, mode=mode, features=features)
1469-
elif child.type == syms.atom:
1479+
elif child.type == syms.atom and not (
1480+
"in" in parens_after
1481+
and len(child.children) == 3
1482+
and is_lpar_token(child.children[0])
1483+
and is_rpar_token(child.children[-1])
1484+
and child.children[1].type == syms.test
1485+
):
14701486
if maybe_make_parens_invisible_in_atom(
14711487
child, parent=node, mode=mode, features=features
14721488
):

src/black/mode.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ class Preview(Enum):
231231
multiline_string_handling = auto()
232232
always_one_newline_after_import = auto()
233233
fix_fmt_skip_in_one_liners = auto()
234+
wrap_comprehension_in = auto()
234235
# Remove parentheses around multiple exception types in except and
235236
# except* without as. See PEP 758 for details.
236237
remove_parens_around_except_types = auto()

src/black/resources/black.schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
"multiline_string_handling",
8787
"always_one_newline_after_import",
8888
"fix_fmt_skip_in_one_liners",
89+
"wrap_comprehension_in",
8990
"remove_parens_around_except_types"
9091
]
9192
},
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# flags: --preview --line-length=79
2+
3+
[a for graph_path_expression in refined_constraint.condition_as_predicate.variables]
4+
[
5+
a
6+
for graph_path_expression in refined_constraint.condition_as_predicate.variables
7+
]
8+
[
9+
a
10+
for graph_path_expression
11+
in refined_constraint.condition_as_predicate.variables
12+
]
13+
[
14+
a
15+
for graph_path_expression in (
16+
refined_constraint.condition_as_predicate.variables
17+
)
18+
]
19+
20+
[
21+
(foobar_very_long_key, foobar_very_long_value)
22+
for foobar_very_long_key, foobar_very_long_value in foobar_very_long_dictionary.items()
23+
]
24+
25+
# Don't split the `in` if it's not too long
26+
lcomp3 = [
27+
element.split("\n", 1)[0]
28+
for element in collection.select_elements()
29+
# right
30+
if element is not None
31+
]
32+
33+
# Don't remove parens around ternaries
34+
expected = [i for i in (a if b else c)]
35+
36+
# Nested arrays
37+
# First in will not be split because it would still be too long
38+
[[
39+
x
40+
for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
41+
for y in xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
42+
]]
43+
44+
# Multiple comprehensions, only split the second `in`
45+
graph_path_expressions_in_local_constraint_refinements = [
46+
graph_path_expression
47+
for refined_constraint in self._local_constraint_refinements.values()
48+
if refined_constraint is not None
49+
for graph_path_expression in refined_constraint.condition_as_predicate.variables
50+
]
51+
52+
# Dictionary comprehensions
53+
dict_with_really_long_names = {
54+
really_really_long_key_name: an_even_longer_really_really_long_key_value
55+
for really_really_long_key_name, an_even_longer_really_really_long_key_value in really_really_really_long_dict_name.items()
56+
}
57+
{
58+
key_with_super_really_long_name: key_with_super_really_long_name
59+
for key_with_super_really_long_name in dictionary_with_super_really_long_name
60+
}
61+
{
62+
key_with_super_really_long_name: key_with_super_really_long_name
63+
for key_with_super_really_long_name
64+
in dictionary_with_super_really_long_name
65+
}
66+
{
67+
key_with_super_really_long_name: key_with_super_really_long_name
68+
for key in (
69+
dictionary
70+
)
71+
}
72+
73+
# output
74+
[
75+
a
76+
for graph_path_expression in (
77+
refined_constraint.condition_as_predicate.variables
78+
)
79+
]
80+
[
81+
a
82+
for graph_path_expression in (
83+
refined_constraint.condition_as_predicate.variables
84+
)
85+
]
86+
[
87+
a
88+
for graph_path_expression in (
89+
refined_constraint.condition_as_predicate.variables
90+
)
91+
]
92+
[
93+
a
94+
for graph_path_expression in (
95+
refined_constraint.condition_as_predicate.variables
96+
)
97+
]
98+
99+
[
100+
(foobar_very_long_key, foobar_very_long_value)
101+
for foobar_very_long_key, foobar_very_long_value in (
102+
foobar_very_long_dictionary.items()
103+
)
104+
]
105+
106+
# Don't split the `in` if it's not too long
107+
lcomp3 = [
108+
element.split("\n", 1)[0]
109+
for element in collection.select_elements()
110+
# right
111+
if element is not None
112+
]
113+
114+
# Don't remove parens around ternaries
115+
expected = [i for i in (a if b else c)]
116+
117+
# Nested arrays
118+
# First in will not be split because it would still be too long
119+
[
120+
[
121+
x
122+
for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
123+
for y in (
124+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
125+
)
126+
]
127+
]
128+
129+
# Multiple comprehensions, only split the second `in`
130+
graph_path_expressions_in_local_constraint_refinements = [
131+
graph_path_expression
132+
for refined_constraint in self._local_constraint_refinements.values()
133+
if refined_constraint is not None
134+
for graph_path_expression in (
135+
refined_constraint.condition_as_predicate.variables
136+
)
137+
]
138+
139+
# Dictionary comprehensions
140+
dict_with_really_long_names = {
141+
really_really_long_key_name: an_even_longer_really_really_long_key_value
142+
for really_really_long_key_name, an_even_longer_really_really_long_key_value in (
143+
really_really_really_long_dict_name.items()
144+
)
145+
}
146+
{
147+
key_with_super_really_long_name: key_with_super_really_long_name
148+
for key_with_super_really_long_name in (
149+
dictionary_with_super_really_long_name
150+
)
151+
}
152+
{
153+
key_with_super_really_long_name: key_with_super_really_long_name
154+
for key_with_super_really_long_name in (
155+
dictionary_with_super_really_long_name
156+
)
157+
}
158+
{
159+
key_with_super_really_long_name: key_with_super_really_long_name
160+
for key in dictionary
161+
}

0 commit comments

Comments
 (0)