Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/black/linegen.py: 11%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Generating lines of code.
3"""
5import re
6import sys
7from collections.abc import Collection, Iterable, Iterator
8from dataclasses import replace
9from enum import Enum, auto
10from functools import partial, wraps
11from typing import Union, cast
13from black.brackets import (
14 COMMA_PRIORITY,
15 COMPARATOR_PRIORITY,
16 DOT_PRIORITY,
17 STRING_PRIORITY,
18 get_leaves_inside_matching_brackets,
19 max_delimiter_priority_in_atom,
20)
21from black.comments import (
22 FMT_OFF,
23 FMT_ON,
24 contains_fmt_directive,
25 generate_comments,
26 list_comments,
27)
28from black.lines import (
29 Line,
30 RHSResult,
31 append_leaves,
32 can_be_split,
33 can_omit_invisible_parens,
34 is_line_short_enough,
35 line_to_string,
36)
37from black.mode import Feature, Mode, Preview
38from black.nodes import (
39 ASSIGNMENTS,
40 BRACKETS,
41 CLOSING_BRACKETS,
42 OPENING_BRACKETS,
43 STANDALONE_COMMENT,
44 STATEMENT,
45 WHITESPACE,
46 Visitor,
47 ensure_visible,
48 fstring_tstring_to_string,
49 get_annotation_type,
50 has_sibling_with_type,
51 is_arith_like,
52 is_async_stmt_or_funcdef,
53 is_atom_with_invisible_parens,
54 is_docstring,
55 is_empty_tuple,
56 is_generator,
57 is_lpar_token,
58 is_multiline_string,
59 is_name_token,
60 is_one_sequence_between,
61 is_one_tuple,
62 is_parent_function_or_class,
63 is_part_of_annotation,
64 is_rpar_token,
65 is_stub_body,
66 is_stub_suite,
67 is_tuple,
68 is_tuple_containing_star,
69 is_tuple_containing_walrus,
70 is_type_ignore_comment,
71 is_type_ignore_comment_string,
72 is_vararg,
73 is_walrus_assignment,
74 is_yield,
75 syms,
76 wrap_in_parentheses,
77)
78from black.numerics import normalize_numeric_literal
79from black.strings import (
80 fix_multiline_docstring,
81 get_string_prefix,
82 normalize_string_prefix,
83 normalize_string_quotes,
84 normalize_unicode_escape_sequences,
85 str_width,
86)
87from black.trans import (
88 CannotTransform,
89 StringMerger,
90 StringParenStripper,
91 StringParenWrapper,
92 StringSplitter,
93 Transformer,
94 hug_power_op,
95)
96from blib2to3.pgen2 import token
97from blib2to3.pytree import Leaf, Node
99# types
100LeafID = int
101LN = Union[Leaf, Node]
104class CannotSplit(CannotTransform):
105 """A readable split that fits the allotted line length is impossible."""
108# This isn't a dataclass because @dataclass + Generic breaks mypyc.
109# See also https://github.com/mypyc/mypyc/issues/827.
110class LineGenerator(Visitor[Line]):
111 """Generates reformatted Line objects. Empty lines are not emitted.
113 Note: destroys the tree it's visiting by mutating prefixes of its leaves
114 in ways that will no longer stringify to valid Python code on the tree.
115 """
117 def __init__(self, mode: Mode, features: Collection[Feature]) -> None:
118 self.mode = mode
119 self.features = features
120 self.current_line: Line
121 self.__post_init__()
123 def line(self, indent: int = 0) -> Iterator[Line]:
124 """Generate a line.
126 If the line is empty, only emit if it makes sense.
127 If the line is too long, split it first and then generate.
129 If any lines were generated, set up a new current_line.
130 """
131 if not self.current_line:
132 self.current_line.depth += indent
133 return # Line is empty, don't emit. Creating a new one unnecessary.
135 if len(self.current_line.leaves) == 1 and is_async_stmt_or_funcdef(
136 self.current_line.leaves[0]
137 ):
138 # Special case for async def/for/with statements. `visit_async_stmt`
139 # adds an `ASYNC` leaf then visits the child def/for/with statement
140 # nodes. Line yields from those nodes shouldn't treat the former
141 # `ASYNC` leaf as a complete line.
142 return
144 complete_line = self.current_line
145 self.current_line = Line(mode=self.mode, depth=complete_line.depth + indent)
146 yield complete_line
148 def visit_default(self, node: LN) -> Iterator[Line]:
149 """Default `visit_*()` implementation. Recurses to children of `node`."""
150 if isinstance(node, Leaf):
151 any_open_brackets = self.current_line.bracket_tracker.any_open_brackets()
152 for comment in generate_comments(node, mode=self.mode):
153 if any_open_brackets:
154 # any comment within brackets is subject to splitting
155 self.current_line.append(comment)
156 elif comment.type == token.COMMENT:
157 # regular trailing comment
158 self.current_line.append(comment)
159 yield from self.line()
161 else:
162 # regular standalone comment
163 yield from self.line()
165 self.current_line.append(comment)
166 yield from self.line()
168 if any_open_brackets:
169 node.prefix = ""
170 if node.type not in WHITESPACE:
171 self.current_line.append(node)
172 yield from super().visit_default(node)
174 def visit_test(self, node: Node) -> Iterator[Line]:
175 """Visit an `x if y else z` test"""
177 already_parenthesized = (
178 node.prev_sibling and node.prev_sibling.type == token.LPAR
179 )
181 if not already_parenthesized:
182 # Similar to logic in wrap_in_parentheses
183 lpar = Leaf(token.LPAR, "")
184 rpar = Leaf(token.RPAR, "")
185 prefix = node.prefix
186 node.prefix = ""
187 lpar.prefix = prefix
188 node.insert_child(0, lpar)
189 node.append_child(rpar)
191 yield from self.visit_default(node)
193 def visit_INDENT(self, node: Leaf) -> Iterator[Line]:
194 """Increase indentation level, maybe yield a line."""
195 # In blib2to3 INDENT never holds comments.
196 yield from self.line(+1)
197 yield from self.visit_default(node)
199 def visit_DEDENT(self, node: Leaf) -> Iterator[Line]:
200 """Decrease indentation level, maybe yield a line."""
201 # The current line might still wait for trailing comments. At DEDENT time
202 # there won't be any (they would be prefixes on the preceding NEWLINE).
203 # Emit the line then.
204 yield from self.line()
206 # While DEDENT has no value, its prefix may contain standalone comments
207 # that belong to the current indentation level. Get 'em.
208 yield from self.visit_default(node)
210 # Finally, emit the dedent.
211 yield from self.line(-1)
213 def visit_stmt(
214 self, node: Node, keywords: set[str], parens: set[str]
215 ) -> Iterator[Line]:
216 """Visit a statement.
218 This implementation is shared for `if`, `while`, `for`, `try`, `except`,
219 `def`, `with`, `class`, `assert`, and assignments.
221 The relevant Python language `keywords` for a given statement
222 appear as NAME leaves within it. This method puts those on a
223 separate line.
225 `parens` holds a set of string leaf values immediately after which
226 invisible parens should be put.
227 """
228 normalize_invisible_parens(
229 node, parens_after=parens, mode=self.mode, features=self.features
230 )
231 for child in node.children:
232 if is_name_token(child) and child.value in keywords:
233 yield from self.line()
235 yield from self.visit(child)
237 def visit_typeparams(self, node: Node) -> Iterator[Line]:
238 yield from self.visit_default(node)
239 node.children[0].prefix = ""
241 def visit_typevartuple(self, node: Node) -> Iterator[Line]:
242 yield from self.visit_default(node)
243 node.children[1].prefix = ""
245 def visit_paramspec(self, node: Node) -> Iterator[Line]:
246 yield from self.visit_default(node)
247 node.children[1].prefix = ""
249 def visit_dictsetmaker(self, node: Node) -> Iterator[Line]:
250 if Preview.wrap_long_dict_values_in_parens in self.mode:
251 for i, child in enumerate(node.children):
252 if i == 0:
253 continue
254 if node.children[i - 1].type == token.COLON:
255 if (
256 child.type == syms.atom
257 and child.children[0].type in OPENING_BRACKETS
258 and not is_walrus_assignment(child)
259 ):
260 maybe_make_parens_invisible_in_atom(
261 child,
262 parent=node,
263 mode=self.mode,
264 features=self.features,
265 remove_brackets_around_comma=False,
266 )
267 else:
268 wrap_in_parentheses(node, child, visible=False, index=i)
269 yield from self.visit_default(node)
271 def visit_funcdef(self, node: Node) -> Iterator[Line]:
272 """Visit function definition."""
273 yield from self.line()
275 # Remove redundant brackets around return type annotation.
276 is_return_annotation = False
277 for child in node.children:
278 if child.type == token.RARROW:
279 is_return_annotation = True
280 elif is_return_annotation:
281 if child.type == syms.atom and child.children[0].type == token.LPAR:
282 if maybe_make_parens_invisible_in_atom(
283 child,
284 parent=node,
285 mode=self.mode,
286 features=self.features,
287 remove_brackets_around_comma=False,
288 ):
289 wrap_in_parentheses(node, child, visible=False)
290 else:
291 wrap_in_parentheses(node, child, visible=False)
292 is_return_annotation = False
294 for child in node.children:
295 yield from self.visit(child)
297 def visit_match_case(self, node: Node) -> Iterator[Line]:
298 """Visit either a match or case statement."""
299 normalize_invisible_parens(
300 node, parens_after=set(), mode=self.mode, features=self.features
301 )
303 yield from self.line()
304 for child in node.children:
305 yield from self.visit(child)
307 def visit_suite(self, node: Node) -> Iterator[Line]:
308 """Visit a suite."""
309 if is_stub_suite(node):
310 yield from self.visit(node.children[2])
311 else:
312 yield from self.visit_default(node)
314 def visit_simple_stmt(self, node: Node) -> Iterator[Line]:
315 """Visit a statement without nested statements."""
316 prev_type: int | None = None
317 for i, child in enumerate(node.children):
318 if (prev_type is None or prev_type == token.SEMI) and is_arith_like(child):
319 wrap_in_parentheses(node, child, visible=False, index=i)
320 prev_type = child.type
322 if node.parent and node.parent.type in STATEMENT:
323 if is_parent_function_or_class(node) and is_stub_body(node):
324 yield from self.visit_default(node)
325 else:
326 yield from self.line(+1)
327 yield from self.visit_default(node)
328 yield from self.line(-1)
330 else:
331 if node.parent and is_stub_suite(node.parent):
332 node.prefix = ""
333 yield from self.visit_default(node)
334 return
335 yield from self.line()
336 yield from self.visit_default(node)
338 def visit_async_stmt(self, node: Node) -> Iterator[Line]:
339 """Visit `async def`, `async for`, `async with`."""
340 yield from self.line()
342 children = iter(node.children)
343 for child in children:
344 yield from self.visit(child)
346 if child.type == token.ASYNC or child.type == STANDALONE_COMMENT:
347 # STANDALONE_COMMENT happens when `# fmt: skip` is applied on the async
348 # line.
349 break
351 internal_stmt = next(children)
352 yield from self.visit(internal_stmt)
354 def visit_decorators(self, node: Node) -> Iterator[Line]:
355 """Visit decorators."""
356 for child in node.children:
357 yield from self.line()
358 yield from self.visit(child)
360 def visit_power(self, node: Node) -> Iterator[Line]:
361 for idx, leaf in enumerate(node.children[:-1]):
362 next_leaf = node.children[idx + 1]
364 if not isinstance(leaf, Leaf):
365 continue
367 value = leaf.value.lower()
368 if (
369 leaf.type == token.NUMBER
370 and next_leaf.type == syms.trailer
371 # Ensure that we are in an attribute trailer
372 and next_leaf.children[0].type == token.DOT
373 # It shouldn't wrap hexadecimal, binary and octal literals
374 and not value.startswith(("0x", "0b", "0o"))
375 # It shouldn't wrap complex literals
376 and "j" not in value
377 ):
378 wrap_in_parentheses(node, leaf)
380 remove_await_parens(node, mode=self.mode, features=self.features)
382 yield from self.visit_default(node)
384 def visit_SEMI(self, leaf: Leaf) -> Iterator[Line]:
385 """Remove a semicolon and put the other statement on a separate line."""
386 yield from self.line()
388 def visit_ENDMARKER(self, leaf: Leaf) -> Iterator[Line]:
389 """End of file. Process outstanding comments and end with a newline."""
390 yield from self.visit_default(leaf)
391 yield from self.line()
393 def visit_STANDALONE_COMMENT(self, leaf: Leaf) -> Iterator[Line]:
394 any_open_brackets = self.current_line.bracket_tracker.any_open_brackets()
395 if not any_open_brackets:
396 yield from self.line()
397 # STANDALONE_COMMENT nodes created by our special handling in
398 # normalize_fmt_off for comment-only blocks have fmt:off as the first
399 # line and fmt:on as the last line (each directive on its own line,
400 # not embedded in other text). These should be appended directly
401 # without calling visit_default, which would process their prefix and
402 # lose indentation. Normal STANDALONE_COMMENT nodes go through
403 # visit_default.
404 value = leaf.value
405 lines = value.splitlines()
406 is_fmt_off_block = (
407 len(lines) >= 2
408 and contains_fmt_directive(lines[0], FMT_OFF)
409 and contains_fmt_directive(lines[-1], FMT_ON)
410 )
411 if is_fmt_off_block:
412 is_after_invisible_lpar = (
413 leaf.fmt_pass_converted_first_leaf is None
414 and len(self.current_line.leaves) == 1
415 and self.current_line.leaves[0].type == token.LPAR
416 and not self.current_line.leaves[0].value
417 )
418 # This is a fmt:off/on block from normalize_fmt_off - we still need
419 # to process any prefix comments (like markdown comments) but append
420 # the fmt block itself directly to preserve its formatting
422 # Only process prefix comments if there actually is a prefix with comments
423 if leaf.prefix and any(
424 line.strip().startswith("#")
425 and not contains_fmt_directive(line.strip())
426 for line in leaf.prefix.split("\n")
427 ):
428 for comment in generate_comments(leaf, mode=self.mode):
429 yield from self.line()
430 self.current_line.append(comment)
431 yield from self.line()
432 # Clear the prefix since we've processed it as comments above
433 leaf.prefix = ""
435 self.current_line.append(leaf)
436 if not any_open_brackets or is_after_invisible_lpar:
437 yield from self.line()
438 else:
439 # Normal standalone comment - process through visit_default
440 yield from self.visit_default(leaf)
442 def visit_factor(self, node: Node) -> Iterator[Line]:
443 """Force parentheses between a unary op and a binary power:
445 -2 ** 8 -> -(2 ** 8)
446 """
447 _operator, operand = node.children
448 if (
449 operand.type == syms.power
450 and len(operand.children) == 3
451 and operand.children[1].type == token.DOUBLESTAR
452 ):
453 lpar = Leaf(token.LPAR, "(")
454 rpar = Leaf(token.RPAR, ")")
455 index = operand.remove() or 0
456 node.insert_child(index, Node(syms.atom, [lpar, operand, rpar]))
457 yield from self.visit_default(node)
459 def visit_tname(self, node: Node) -> Iterator[Line]:
460 """
461 Add potential parentheses around types in function parameter lists to be made
462 into real parentheses in case the type hint is too long to fit on a line
463 Examples:
464 def foo(a: int, b: float = 7): ...
466 ->
468 def foo(a: (int), b: (float) = 7): ...
469 """
470 if len(node.children) == 3 and maybe_make_parens_invisible_in_atom(
471 node.children[2], parent=node, mode=self.mode, features=self.features
472 ):
473 wrap_in_parentheses(node, node.children[2], visible=False)
475 yield from self.visit_default(node)
477 def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
478 normalize_unicode_escape_sequences(leaf)
480 if is_docstring(leaf) and not re.search(r"\\\s*\n", leaf.value):
481 # We're ignoring docstrings with backslash newline escapes because changing
482 # indentation of those changes the AST representation of the code.
483 if self.mode.string_normalization:
484 docstring = normalize_string_prefix(leaf.value)
485 # We handle string normalization at the end of this method, but since
486 # what we do right now acts differently depending on quote style (ex.
487 # see padding logic below), there's a possibility for unstable
488 # formatting. To avoid a situation where this function formats a
489 # docstring differently on the second pass, normalize it early.
490 docstring = normalize_string_quotes(docstring)
491 else:
492 docstring = leaf.value
493 prefix = get_string_prefix(docstring)
494 docstring = docstring[len(prefix) :] # Remove the prefix
495 quote_char = docstring[0]
496 # A natural way to remove the outer quotes is to do:
497 # docstring = docstring.strip(quote_char)
498 # but that breaks on """""x""" (which is '""x').
499 # So we actually need to remove the first character and the next two
500 # characters but only if they are the same as the first.
501 quote_len = 1 if docstring[1] != quote_char else 3
502 docstring = docstring[quote_len:-quote_len]
503 docstring_started_empty = not docstring
504 indent = " " * 4 * self.current_line.depth
506 if is_multiline_string(leaf):
507 docstring = fix_multiline_docstring(docstring, indent)
508 else:
509 docstring = docstring.strip()
511 has_trailing_backslash = False
512 if docstring:
513 # Add some padding if the docstring starts / ends with a quote mark.
514 if docstring[0] == quote_char:
515 docstring = " " + docstring
516 if docstring[-1] == quote_char:
517 docstring += " "
518 if docstring[-1] == "\\":
519 backslash_count = len(docstring) - len(docstring.rstrip("\\"))
520 if backslash_count % 2:
521 # Odd number of tailing backslashes, add some padding to
522 # avoid escaping the closing string quote.
523 docstring += " "
524 has_trailing_backslash = True
525 elif not docstring_started_empty:
526 docstring = " "
528 # We could enforce triple quotes at this point.
529 quote = quote_char * quote_len
531 # It's invalid to put closing single-character quotes on a new line.
532 if quote_len == 3:
533 # We need to find the length of the last line of the docstring
534 # to find if we can add the closing quotes to the line without
535 # exceeding the maximum line length.
536 # If docstring is one line, we don't put the closing quotes on a
537 # separate line because it looks ugly (#3320).
538 lines = docstring.splitlines()
539 last_line_length = len(lines[-1]) if docstring else 0
541 # If adding closing quotes would cause the last line to exceed
542 # the maximum line length, and the closing quote is not
543 # prefixed by a newline then put a line break before
544 # the closing quotes
545 if (
546 len(lines) > 1
547 and last_line_length + quote_len > self.mode.line_length
548 and len(indent) + quote_len <= self.mode.line_length
549 and not has_trailing_backslash
550 ):
551 if leaf.value[-1 - quote_len] == "\n":
552 leaf.value = prefix + quote + docstring + quote
553 else:
554 leaf.value = prefix + quote + docstring + "\n" + indent + quote
555 else:
556 leaf.value = prefix + quote + docstring + quote
557 else:
558 leaf.value = prefix + quote + docstring + quote
560 if self.mode.string_normalization and leaf.type == token.STRING:
561 leaf.value = normalize_string_prefix(leaf.value)
562 leaf.value = normalize_string_quotes(leaf.value)
563 yield from self.visit_default(leaf)
565 def visit_NUMBER(self, leaf: Leaf) -> Iterator[Line]:
566 normalize_numeric_literal(leaf)
567 yield from self.visit_default(leaf)
569 def visit_atom(self, node: Node) -> Iterator[Line]:
570 """Visit any atom"""
571 if len(node.children) == 3:
572 first = node.children[0]
573 last = node.children[-1]
574 if (first.type == token.LSQB and last.type == token.RSQB) or (
575 first.type == token.LBRACE and last.type == token.RBRACE
576 ):
577 # Lists or sets of one item
578 maybe_make_parens_invisible_in_atom(
579 node.children[1],
580 parent=node,
581 mode=self.mode,
582 features=self.features,
583 )
585 yield from self.visit_default(node)
587 def visit_fstring(self, node: Node) -> Iterator[Line]:
588 # If the fstring was converted to a STANDALONE_COMMENT by
589 # normalize_fmt_off (e.g. it was inside a # fmt: off block),
590 # skip the fstring-to-string conversion and just visit normally.
591 if any(child.type == STANDALONE_COMMENT for child in node.children):
592 yield from self.visit_default(node)
593 return
594 # currently we don't want to format and split f-strings at all.
595 string_leaf = fstring_tstring_to_string(node)
596 node.replace(string_leaf)
597 if "\\" in string_leaf.value and any(
598 "\\" in str(child)
599 for child in node.children
600 if child.type == syms.fstring_replacement_field
601 ):
602 # string normalization doesn't account for nested quotes,
603 # causing breakages. skip normalization when nested quotes exist
604 yield from self.visit_default(string_leaf)
605 return
606 yield from self.visit_STRING(string_leaf)
608 def visit_tstring(self, node: Node) -> Iterator[Line]:
609 # If the tstring was converted to a STANDALONE_COMMENT by
610 # normalize_fmt_off, skip the conversion and just visit normally.
611 if any(child.type == STANDALONE_COMMENT for child in node.children):
612 yield from self.visit_default(node)
613 return
614 # currently we don't want to format and split t-strings at all.
615 string_leaf = fstring_tstring_to_string(node)
616 node.replace(string_leaf)
617 if "\\" in string_leaf.value and any(
618 "\\" in str(child)
619 for child in node.children
620 if child.type == syms.tstring_replacement_field
621 ):
622 # string normalization doesn't account for nested quotes,
623 # causing breakages. skip normalization when nested quotes exist
624 yield from self.visit_default(string_leaf)
625 return
626 yield from self.visit_STRING(string_leaf)
628 # TODO: Uncomment Implementation to format f-string children
629 # fstring_start = node.children[0]
630 # fstring_end = node.children[-1]
631 # assert isinstance(fstring_start, Leaf)
632 # assert isinstance(fstring_end, Leaf)
634 # quote_char = fstring_end.value[0]
635 # quote_idx = fstring_start.value.index(quote_char)
636 # prefix, quote = (
637 # fstring_start.value[:quote_idx],
638 # fstring_start.value[quote_idx:]
639 # )
641 # if not is_docstring(node, self.mode):
642 # prefix = normalize_string_prefix(prefix)
644 # assert quote == fstring_end.value
646 # is_raw_fstring = "r" in prefix or "R" in prefix
647 # middles = [
648 # leaf
649 # for leaf in node.leaves()
650 # if leaf.type == token.FSTRING_MIDDLE
651 # ]
653 # if self.mode.string_normalization:
654 # middles, quote = normalize_fstring_quotes(quote, middles, is_raw_fstring)
656 # fstring_start.value = prefix + quote
657 # fstring_end.value = quote
659 # yield from self.visit_default(node)
661 def visit_comp_for(self, node: Node) -> Iterator[Line]:
662 if Preview.wrap_comprehension_in in self.mode:
663 normalize_invisible_parens(
664 node, parens_after={"in"}, mode=self.mode, features=self.features
665 )
666 yield from self.visit_default(node)
668 def visit_old_comp_for(self, node: Node) -> Iterator[Line]:
669 yield from self.visit_comp_for(node)
671 def __post_init__(self) -> None:
672 """You are in a twisty little maze of passages."""
673 self.current_line = Line(mode=self.mode)
675 v = self.visit_stmt
676 Ø: set[str] = set()
677 self.visit_assert_stmt = partial(v, keywords={"assert"}, parens={"assert", ","})
678 self.visit_if_stmt = partial(
679 v, keywords={"if", "else", "elif"}, parens={"if", "elif"}
680 )
681 self.visit_while_stmt = partial(v, keywords={"while", "else"}, parens={"while"})
682 self.visit_for_stmt = partial(v, keywords={"for", "else"}, parens={"for", "in"})
683 self.visit_try_stmt = partial(
684 v, keywords={"try", "except", "else", "finally"}, parens=Ø
685 )
686 self.visit_except_clause = partial(v, keywords={"except"}, parens={"except"})
687 self.visit_with_stmt = partial(v, keywords={"with"}, parens={"with"})
688 self.visit_classdef = partial(v, keywords={"class"}, parens=Ø)
690 self.visit_expr_stmt = partial(v, keywords=Ø, parens=ASSIGNMENTS)
691 self.visit_return_stmt = partial(v, keywords={"return"}, parens={"return"})
692 self.visit_yield_expr = partial(
693 v,
694 keywords=Ø,
695 parens=(
696 {"yield"} if Preview.parenthesize_tuple_in_yield in self.mode else Ø
697 ),
698 )
699 self.visit_import_from = partial(v, keywords=Ø, parens={"import"})
700 self.visit_del_stmt = partial(v, keywords=Ø, parens={"del"})
701 self.visit_async_funcdef = self.visit_async_stmt
702 self.visit_decorated = self.visit_decorators
704 # PEP 634
705 self.visit_match_stmt = self.visit_match_case
706 self.visit_case_block = self.visit_match_case
707 self.visit_guard = partial(v, keywords=Ø, parens={"if"})
710# Remove when `simplify_power_operator_hugging` becomes stable.
711def _hugging_power_ops_line_to_string(
712 line: Line,
713 features: Collection[Feature],
714 mode: Mode,
715) -> str | None:
716 try:
717 return line_to_string(next(hug_power_op(line, features, mode)))
718 except CannotTransform:
719 return None
722def transform_line(
723 line: Line, mode: Mode, features: Collection[Feature] = ()
724) -> Iterator[Line]:
725 """Transform a `line`, potentially splitting it into many lines.
727 They should fit in the allotted `line_length` but might not be able to.
729 `features` are syntactical features that may be used in the output.
730 """
731 if line.is_comment:
732 yield line
733 return
735 line_str = line_to_string(line)
737 if Preview.simplify_power_operator_hugging in mode:
738 line_str_hugging_power_ops = line_str
739 else:
740 # We need the line string when power operators are hugging to determine if we
741 # should split the line. Default to line_str, if no power operator are present
742 # on the line.
743 line_str_hugging_power_ops = (
744 _hugging_power_ops_line_to_string(line, features, mode) or line_str
745 )
747 ll = mode.line_length
748 sn = mode.string_normalization
749 string_merge = StringMerger(ll, sn)
750 string_paren_strip = StringParenStripper(ll, sn)
751 string_split = StringSplitter(ll, sn)
752 string_paren_wrap = StringParenWrapper(ll, sn)
754 transformers: list[Transformer]
755 if (
756 not line.contains_uncollapsable_type_comments()
757 and not line.should_split_rhs
758 and not line.magic_trailing_comma
759 and (
760 is_line_short_enough(line, mode=mode, line_str=line_str_hugging_power_ops)
761 or line.contains_unsplittable_type_ignore()
762 )
763 and not (line.inside_brackets and line.contains_standalone_comments())
764 and not line.contains_implicit_multiline_string_with_comments()
765 ):
766 # Only apply basic string preprocessing, since lines shouldn't be split here.
767 if Preview.string_processing in mode:
768 transformers = [string_merge, string_paren_strip]
769 else:
770 transformers = []
771 elif line.is_def and not should_split_funcdef_with_rhs(line, mode):
772 transformers = [left_hand_split]
773 else:
775 def _rhs(
776 self: object, line: Line, features: Collection[Feature], mode: Mode
777 ) -> Iterator[Line]:
778 """Wraps calls to `right_hand_split`.
780 The calls increasingly `omit` right-hand trailers (bracket pairs with
781 content), meaning the trailers get glued together to split on another
782 bracket pair instead.
783 """
784 for omit in generate_trailers_to_omit(line, mode.line_length):
785 lines = list(right_hand_split(line, mode, features, omit=omit))
786 # Note: this check is only able to figure out if the first line of the
787 # *current* transformation fits in the line length. This is true only
788 # for simple cases. All others require running more transforms via
789 # `transform_line()`. This check doesn't know if those would succeed.
790 if is_line_short_enough(lines[0], mode=mode) or (
791 omit and _over_length_only_due_to_subscript_comment(lines[0], mode)
792 ):
793 yield from lines
794 return
796 # All splits failed, best effort split with no omits.
797 # This mostly happens to multiline strings that are by definition
798 # reported as not fitting a single line, as well as lines that contain
799 # trailing commas (those have to be exploded).
800 yield from right_hand_split(line, mode, features=features)
802 # HACK: nested functions (like _rhs) compiled by mypyc don't retain their
803 # __name__ attribute which is needed in `run_transformer` further down.
804 # Unfortunately a nested class breaks mypyc too. So a class must be created
805 # via type ... https://github.com/mypyc/mypyc/issues/884
806 rhs = type("rhs", (), {"__call__": _rhs})()
808 if Preview.string_processing in mode:
809 if line.inside_brackets:
810 transformers = [
811 string_merge,
812 string_paren_strip,
813 string_split,
814 delimiter_split,
815 standalone_comment_split,
816 string_paren_wrap,
817 rhs,
818 ]
819 else:
820 transformers = [
821 string_merge,
822 string_paren_strip,
823 string_split,
824 string_paren_wrap,
825 rhs,
826 ]
827 else:
828 if line.inside_brackets:
829 transformers = [delimiter_split, standalone_comment_split, rhs]
830 else:
831 transformers = [rhs]
833 if Preview.simplify_power_operator_hugging not in mode:
834 # It's always safe to attempt hugging of power operations and pretty much every
835 # line could match.
836 transformers.append(hug_power_op)
838 for transform in transformers:
839 # We are accumulating lines in `result` because we might want to abort
840 # mission and return the original line in the end, or attempt a different
841 # split altogether.
842 try:
843 result = run_transformer(line, transform, mode, features, line_str=line_str)
844 except CannotTransform:
845 continue
846 else:
847 yield from result
848 break
850 else:
851 # A leftover standalone comment would render inline and break the
852 # second pass with a parse error (#4296). Force a split so the
853 # output is at least valid Python.
854 if line.contains_standalone_comments():
855 yield from _force_standalone_comment_split(line)
856 else:
857 yield line
860def should_split_funcdef_with_rhs(line: Line, mode: Mode) -> bool:
861 """If a funcdef has a magic trailing comma in the return type, then we should first
862 split the line with rhs to respect the comma.
863 """
864 return_type_leaves: list[Leaf] = []
865 in_return_type = False
867 for leaf in line.leaves:
868 if leaf.type == token.COLON:
869 in_return_type = False
870 if in_return_type:
871 return_type_leaves.append(leaf)
872 if leaf.type == token.RARROW:
873 in_return_type = True
875 # using `bracket_split_build_line` will mess with whitespace, so we duplicate a
876 # couple lines from it.
877 result = Line(mode=line.mode, depth=line.depth)
878 leaves_to_track = get_leaves_inside_matching_brackets(return_type_leaves)
879 for leaf in return_type_leaves:
880 result.append(
881 leaf,
882 preformatted=True,
883 track_bracket=id(leaf) in leaves_to_track,
884 )
886 # we could also return true if the line is too long, and the return type is longer
887 # than the param list. Or if `should_split_rhs` returns True.
888 return result.magic_trailing_comma is not None
891class _BracketSplitComponent(Enum):
892 head = auto()
893 body = auto()
894 tail = auto()
897def left_hand_split(
898 line: Line, _features: Collection[Feature], mode: Mode
899) -> Iterator[Line]:
900 """Split line into many lines, starting with the first matching bracket pair.
902 Note: this usually looks weird, only use this for function definitions.
903 Prefer RHS otherwise. This is why this function is not symmetrical with
904 :func:`right_hand_split` which also handles optional parentheses.
905 """
906 for leaf_type in [token.LPAR, token.LSQB]:
907 tail_leaves: list[Leaf] = []
908 body_leaves: list[Leaf] = []
909 head_leaves: list[Leaf] = []
910 current_leaves = head_leaves
911 matching_bracket: Leaf | None = None
912 depth = 0
913 for index, leaf in enumerate(line.leaves):
914 if index == 2 and leaf.type == token.LSQB:
915 # A [ at index 2 means this is a type param, so start
916 # tracking the depth
917 depth += 1
918 elif depth > 0:
919 if leaf.type == token.LSQB:
920 depth += 1
921 elif leaf.type == token.RSQB:
922 depth -= 1
923 if (
924 current_leaves is body_leaves
925 and leaf.type in CLOSING_BRACKETS
926 and leaf.opening_bracket is matching_bracket
927 and isinstance(matching_bracket, Leaf)
928 # If the code is still on LPAR and we are inside a type
929 # param, ignore the match since this is searching
930 # for the function arguments
931 and not (leaf_type == token.LPAR and depth > 0)
932 ):
933 ensure_visible(leaf)
934 ensure_visible(matching_bracket)
935 current_leaves = tail_leaves if body_leaves else head_leaves
936 current_leaves.append(leaf)
937 if current_leaves is head_leaves:
938 if leaf.type == leaf_type and (
939 not (leaf_type == token.LPAR and depth > 0)
940 ):
941 matching_bracket = leaf
942 current_leaves = body_leaves
943 if matching_bracket and tail_leaves:
944 break
945 if not matching_bracket or not tail_leaves:
946 raise CannotSplit("No brackets found")
948 head = bracket_split_build_line(
949 head_leaves, line, matching_bracket, component=_BracketSplitComponent.head
950 )
951 body = bracket_split_build_line(
952 body_leaves, line, matching_bracket, component=_BracketSplitComponent.body
953 )
954 tail = bracket_split_build_line(
955 tail_leaves, line, matching_bracket, component=_BracketSplitComponent.tail
956 )
957 bracket_split_succeeded_or_raise(head, body, tail)
958 for result in (head, body, tail):
959 if result:
960 yield result
963def right_hand_split(
964 line: Line,
965 mode: Mode,
966 features: Collection[Feature] = (),
967 omit: Collection[LeafID] = (),
968) -> Iterator[Line]:
969 """Split line into many lines, starting with the last matching bracket pair.
971 If the split was by optional parentheses, attempt splitting without them, too.
972 `omit` is a collection of closing bracket IDs that shouldn't be considered for
973 this split.
975 Note: running this function modifies `bracket_depth` on the leaves of `line`.
976 """
977 rhs_result = _first_right_hand_split(line, omit=omit)
978 yield from _maybe_split_omitting_optional_parens(
979 rhs_result, line, mode, features=features, omit=omit
980 )
983def _first_right_hand_split(
984 line: Line,
985 omit: Collection[LeafID] = (),
986) -> RHSResult:
987 """Split the line into head, body, tail starting with the last bracket pair.
989 Note: this function should not have side effects. It's relied upon by
990 _maybe_split_omitting_optional_parens to get an opinion whether to prefer
991 splitting on the right side of an assignment statement.
992 """
993 tail_leaves: list[Leaf] = []
994 body_leaves: list[Leaf] = []
995 head_leaves: list[Leaf] = []
996 current_leaves = tail_leaves
997 opening_bracket: Leaf | None = None
998 closing_bracket: Leaf | None = None
999 for leaf in reversed(line.leaves):
1000 if current_leaves is body_leaves:
1001 if leaf is opening_bracket:
1002 current_leaves = head_leaves if body_leaves else tail_leaves
1003 current_leaves.append(leaf)
1004 if current_leaves is tail_leaves:
1005 if leaf.type in CLOSING_BRACKETS and id(leaf) not in omit:
1006 opening_bracket = leaf.opening_bracket
1007 closing_bracket = leaf
1008 current_leaves = body_leaves
1009 if not (opening_bracket and closing_bracket and head_leaves):
1010 # If there is no opening or closing_bracket that means the split failed and
1011 # all content is in the tail. Otherwise, if `head_leaves` are empty, it means
1012 # the matching `opening_bracket` wasn't available on `line` anymore.
1013 raise CannotSplit("No brackets found")
1015 tail_leaves.reverse()
1016 body_leaves.reverse()
1017 head_leaves.reverse()
1019 body: Line | None = None
1020 if (
1021 Preview.hug_parens_with_braces_and_square_brackets in line.mode
1022 and tail_leaves[0].value
1023 and tail_leaves[0].opening_bracket is head_leaves[-1]
1024 ):
1025 inner_body_leaves = list(body_leaves)
1026 hugged_opening_leaves: list[Leaf] = []
1027 hugged_closing_leaves: list[Leaf] = []
1028 is_unpacking = body_leaves[0].type in [token.STAR, token.DOUBLESTAR]
1029 unpacking_offset: int = 1 if is_unpacking else 0
1030 while (
1031 len(inner_body_leaves) >= 2 + unpacking_offset
1032 and inner_body_leaves[-1].type in CLOSING_BRACKETS
1033 and inner_body_leaves[-1].opening_bracket
1034 is inner_body_leaves[unpacking_offset]
1035 ):
1036 if unpacking_offset:
1037 hugged_opening_leaves.append(inner_body_leaves.pop(0))
1038 unpacking_offset = 0
1039 hugged_opening_leaves.append(inner_body_leaves.pop(0))
1040 hugged_closing_leaves.insert(0, inner_body_leaves.pop())
1042 if hugged_opening_leaves and inner_body_leaves:
1043 inner_body = bracket_split_build_line(
1044 inner_body_leaves,
1045 line,
1046 hugged_opening_leaves[-1],
1047 component=_BracketSplitComponent.body,
1048 )
1049 if (
1050 line.mode.magic_trailing_comma
1051 and inner_body_leaves[-1].type == token.COMMA
1052 ):
1053 should_hug = True
1054 else:
1055 line_length = line.mode.line_length - sum(
1056 len(str(leaf))
1057 for leaf in hugged_opening_leaves + hugged_closing_leaves
1058 )
1059 if is_line_short_enough(
1060 inner_body, mode=replace(line.mode, line_length=line_length)
1061 ):
1062 # Do not hug if it fits on a single line.
1063 should_hug = False
1064 else:
1065 should_hug = True
1066 if should_hug and (
1067 _hugging_merges_type_ignores(line, head_leaves, hugged_opening_leaves)
1068 or _hugging_merges_type_ignores(
1069 line, hugged_closing_leaves, tail_leaves
1070 )
1071 ):
1072 # Hugging joins these leaves onto one physical line, and their
1073 # trailing comments come along. `type: ignore` is recorded per
1074 # line by the AST, so two of them landing on the same line would
1075 # drop one and make the output non-equivalent.
1076 should_hug = False
1077 if should_hug:
1078 body_leaves = inner_body_leaves
1079 head_leaves.extend(hugged_opening_leaves)
1080 tail_leaves = hugged_closing_leaves + tail_leaves
1081 body = inner_body # No need to re-calculate the body again later.
1083 head = bracket_split_build_line(
1084 head_leaves, line, opening_bracket, component=_BracketSplitComponent.head
1085 )
1086 if body is None:
1087 body = bracket_split_build_line(
1088 body_leaves, line, opening_bracket, component=_BracketSplitComponent.body
1089 )
1090 tail = bracket_split_build_line(
1091 tail_leaves, line, opening_bracket, component=_BracketSplitComponent.tail
1092 )
1093 bracket_split_succeeded_or_raise(head, body, tail)
1094 return RHSResult(head, body, tail, opening_bracket, closing_bracket)
1097def _maybe_split_omitting_optional_parens(
1098 rhs: RHSResult,
1099 line: Line,
1100 mode: Mode,
1101 features: Collection[Feature] = (),
1102 omit: Collection[LeafID] = (),
1103) -> Iterator[Line]:
1104 if (
1105 Feature.FORCE_OPTIONAL_PARENTHESES not in features
1106 # the opening bracket is an optional paren
1107 and rhs.opening_bracket.type == token.LPAR
1108 and not rhs.opening_bracket.value
1109 # the closing bracket is an optional paren
1110 and rhs.closing_bracket.type == token.RPAR
1111 and not rhs.closing_bracket.value
1112 # it's not an import (optional parens are the only thing we can split on
1113 # in this case; attempting a split without them is a waste of time)
1114 and not line.is_import
1115 # and we can actually remove the parens
1116 and can_omit_invisible_parens(rhs, mode.line_length, mode)
1117 ):
1118 omit = {id(rhs.closing_bracket), *omit}
1119 try:
1120 # The RHSResult Omitting Optional Parens.
1121 rhs_oop = _first_right_hand_split(line, omit=omit)
1122 if _prefer_split_rhs_oop_over_rhs(rhs_oop, rhs, mode):
1123 yield from _maybe_split_omitting_optional_parens(
1124 rhs_oop, line, mode, features=features, omit=omit
1125 )
1126 return
1128 except CannotSplit as e:
1129 # For chained assignments we want to use the previous successful split
1130 if line.is_chained_assignment:
1131 pass
1133 elif (
1134 not can_be_split(rhs.body)
1135 and not is_line_short_enough(rhs.body, mode=mode)
1136 and not (
1137 Preview.wrap_long_dict_values_in_parens
1138 and rhs.opening_bracket.parent
1139 and rhs.opening_bracket.parent.parent
1140 and rhs.opening_bracket.parent.parent.type == syms.dictsetmaker
1141 )
1142 and not (
1143 rhs.opening_bracket.parent
1144 and rhs.opening_bracket.parent.parent
1145 and rhs.opening_bracket.parent.parent.type == syms.case_block
1146 )
1147 ):
1148 raise CannotSplit(
1149 "Splitting failed, body is still too long and can't be split."
1150 ) from e
1152 elif (
1153 rhs.head.contains_multiline_strings()
1154 or rhs.tail.contains_multiline_strings()
1155 ):
1156 raise CannotSplit(
1157 "The current optional pair of parentheses is bound to fail to"
1158 " satisfy the splitting algorithm because the head or the tail"
1159 " contains multiline strings which by definition never fit one"
1160 " line."
1161 ) from e
1163 ensure_visible(rhs.opening_bracket)
1164 ensure_visible(rhs.closing_bracket)
1165 for result in (rhs.head, rhs.body, rhs.tail):
1166 if result:
1167 yield result
1170def _prefer_split_rhs_oop_over_rhs(
1171 rhs_oop: RHSResult, rhs: RHSResult, mode: Mode
1172) -> bool:
1173 """
1174 Returns whether we should prefer the result from a split omitting optional parens
1175 (rhs_oop) over the original (rhs).
1176 """
1177 # contains unsplittable type ignore
1178 if (
1179 rhs_oop.head.contains_unsplittable_type_ignore()
1180 or rhs_oop.body.contains_unsplittable_type_ignore()
1181 or rhs_oop.tail.contains_unsplittable_type_ignore()
1182 ):
1183 return True
1185 # Retain optional parens around dictionary values
1186 if (
1187 Preview.wrap_long_dict_values_in_parens
1188 and rhs.opening_bracket.parent
1189 and rhs.opening_bracket.parent.parent
1190 and rhs.opening_bracket.parent.parent.type == syms.dictsetmaker
1191 and rhs.body.bracket_tracker.delimiters
1192 ):
1193 # Unless the split is inside the key
1194 return any(leaf.type == token.COLON for leaf in rhs_oop.tail.leaves)
1196 # the split is right after `=`
1197 if not (len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL):
1198 return True
1200 # the left side of assignment contains brackets
1201 if not any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]):
1202 return True
1204 # the left side of assignment is short enough (the -1 is for the ending optional
1205 # paren)
1206 if not is_line_short_enough(
1207 rhs.head, mode=replace(mode, line_length=mode.line_length - 1)
1208 ):
1209 return True
1211 # the left side of assignment won't explode further because of magic trailing comma
1212 if rhs.head.magic_trailing_comma is not None:
1213 return True
1215 # If we have multiple targets, we prefer more `=`s on the head vs pushing them to
1216 # the body
1217 rhs_head_equal_count = [leaf.type for leaf in rhs.head.leaves].count(token.EQUAL)
1218 rhs_oop_head_equal_count = [leaf.type for leaf in rhs_oop.head.leaves].count(
1219 token.EQUAL
1220 )
1221 if rhs_head_equal_count > 1 and rhs_head_equal_count > rhs_oop_head_equal_count:
1222 return False
1224 has_closing_bracket_after_assign = False
1225 for leaf in reversed(rhs_oop.head.leaves):
1226 if leaf.type == token.EQUAL:
1227 break
1228 if leaf.type in CLOSING_BRACKETS:
1229 has_closing_bracket_after_assign = True
1230 break
1231 return (
1232 # contains matching brackets after the `=` (done by checking there is a
1233 # closing bracket)
1234 has_closing_bracket_after_assign
1235 or (
1236 # the split is actually from inside the optional parens (done by checking
1237 # the first line still contains the `=`)
1238 any(leaf.type == token.EQUAL for leaf in rhs_oop.head.leaves)
1239 # the first line is short enough
1240 and is_line_short_enough(rhs_oop.head, mode=mode)
1241 )
1242 )
1245def bracket_split_succeeded_or_raise(head: Line, body: Line, tail: Line) -> None:
1246 """Raise :exc:`CannotSplit` if the last left- or right-hand split failed.
1248 Do nothing otherwise.
1250 A left- or right-hand split is based on a pair of brackets. Content before
1251 (and including) the opening bracket is left on one line, content inside the
1252 brackets is put on a separate line, and finally content starting with and
1253 following the closing bracket is put on a separate line.
1255 Those are called `head`, `body`, and `tail`, respectively. If the split
1256 produced the same line (all content in `head`) or ended up with an empty `body`
1257 and the `tail` is just the closing bracket, then it's considered failed.
1258 """
1259 tail_len = len(str(tail).strip())
1260 if not body:
1261 if tail_len == 0:
1262 raise CannotSplit("Splitting brackets produced the same line")
1264 elif tail_len < 3:
1265 raise CannotSplit(
1266 f"Splitting brackets on an empty body to save {tail_len} characters is"
1267 " not worth it"
1268 )
1271def _ensure_trailing_comma(
1272 leaves: list[Leaf], original: Line, opening_bracket: Leaf
1273) -> bool:
1274 if not leaves:
1275 return False
1276 # Ensure a trailing comma for imports
1277 if original.is_import:
1278 return True
1279 # ...and standalone function arguments
1280 if not original.is_def:
1281 return False
1282 if opening_bracket.value != "(":
1283 return False
1284 # Don't add commas if we already have any commas
1285 if any(
1286 leaf.type == token.COMMA and not is_part_of_annotation(leaf) for leaf in leaves
1287 ):
1288 return False
1290 # Find a leaf with a parent (comments don't have parents)
1291 leaf_with_parent = next((leaf for leaf in leaves if leaf.parent), None)
1292 if leaf_with_parent is None:
1293 return True
1294 # Don't add commas inside parenthesized return annotations
1295 if get_annotation_type(leaf_with_parent) == "return":
1296 return False
1297 # Don't add commas inside PEP 604 unions
1298 if (
1299 leaf_with_parent.parent
1300 and leaf_with_parent.parent.next_sibling
1301 and leaf_with_parent.parent.next_sibling.type == token.VBAR
1302 ):
1303 return False
1304 return True
1307def _hugging_merges_type_ignores(line: Line, *leaf_groups: Iterable[Leaf]) -> bool:
1308 """Return True if hugging these groups would put two `type: ignore`s on a line."""
1309 seen = 0
1310 for leaves in leaf_groups:
1311 for leaf in leaves:
1312 for comment in line.comments_after(leaf):
1313 if is_type_ignore_comment(comment, mode=line.mode):
1314 seen += 1
1315 if seen > 1:
1316 return True
1317 return False
1320def bracket_split_build_line(
1321 leaves: list[Leaf],
1322 original: Line,
1323 opening_bracket: Leaf,
1324 *,
1325 component: _BracketSplitComponent,
1326) -> Line:
1327 """Return a new line with given `leaves` and respective comments from `original`.
1329 If it's the head component, brackets will be tracked so trailing commas are
1330 respected.
1332 If it's the body component, the result line is one-indented inside brackets and as
1333 such has its first leaf's prefix normalized and a trailing comma added when
1334 expected.
1335 """
1336 result = Line(mode=original.mode, depth=original.depth)
1337 if component is _BracketSplitComponent.body:
1338 result.inside_brackets = True
1339 result.depth += 1
1340 if _ensure_trailing_comma(leaves, original, opening_bracket):
1341 for i in range(len(leaves) - 1, -1, -1):
1342 if leaves[i].type == STANDALONE_COMMENT:
1343 continue
1345 if leaves[i].type != token.COMMA:
1346 new_comma = Leaf(token.COMMA, ",")
1347 leaves.insert(i + 1, new_comma)
1348 break
1350 leaves_to_track: set[LeafID] = set()
1351 if component is _BracketSplitComponent.head:
1352 leaves_to_track = get_leaves_inside_matching_brackets(leaves)
1353 # Populate the line
1354 for leaf in leaves:
1355 result.append(
1356 leaf,
1357 preformatted=True,
1358 track_bracket=id(leaf) in leaves_to_track,
1359 )
1360 for comment_after in original.comments_after(leaf):
1361 result.append(comment_after, preformatted=True)
1362 if component is _BracketSplitComponent.body and should_split_line(
1363 result, opening_bracket
1364 ):
1365 result.should_split_rhs = True
1366 return result
1369def dont_increase_indentation(split_func: Transformer) -> Transformer:
1370 """Normalize prefix of the first leaf in every line returned by `split_func`.
1372 This is a decorator over relevant split functions.
1373 """
1375 @wraps(split_func)
1376 def split_wrapper(
1377 line: Line, features: Collection[Feature], mode: Mode
1378 ) -> Iterator[Line]:
1379 for split_line in split_func(line, features, mode):
1380 split_line.leaves[0].prefix = ""
1381 yield split_line
1383 return split_wrapper
1386def _get_last_non_comment_leaf(line: Line) -> int | None:
1387 for leaf_idx in range(len(line.leaves) - 1, 0, -1):
1388 if line.leaves[leaf_idx].type != STANDALONE_COMMENT:
1389 return leaf_idx
1390 return None
1393def _can_add_trailing_comma(leaf: Leaf, features: Collection[Feature]) -> bool:
1394 if is_vararg(leaf, within={syms.typedargslist}):
1395 return Feature.TRAILING_COMMA_IN_DEF in features
1396 if is_vararg(leaf, within={syms.arglist, syms.argument}):
1397 return Feature.TRAILING_COMMA_IN_CALL in features
1398 return True
1401def _safe_add_trailing_comma(safe: bool, delimiter_priority: int, line: Line) -> Line:
1402 if (
1403 safe
1404 and delimiter_priority == COMMA_PRIORITY
1405 and line.leaves[-1].type != token.COMMA
1406 and line.leaves[-1].type != STANDALONE_COMMENT
1407 ):
1408 new_comma = Leaf(token.COMMA, ",")
1409 line.append(new_comma)
1410 return line
1413MIGRATE_COMMENT_DELIMITERS = {STRING_PRIORITY, COMMA_PRIORITY}
1416def _can_defer_lone_comparator_to_rhs(line: Line, mode: Mode) -> bool:
1417 """Return True if the lone comparator on `line` can defer to right_hand_split.
1419 Caller has already established exactly one delimiter at
1420 `COMPARATOR_PRIORITY`. We defer only when:
1422 - the LHS up to the comparator has no opening brackets, so the existing
1423 "break before the comparator" wouldn't produce a balanced two-sided
1424 split anyway, and
1425 - `right_hand_split` would produce a head that fits in the line length,
1426 so we don't strand `if t` on its own line just to push it back onto an
1427 overflowing single line when the RHS bracket can't be exploded
1428 usefully (e.g. an empty `decode()` paren).
1429 """
1430 past_comparator = False
1431 for leaf in line.leaves:
1432 if leaf.type in OPENING_BRACKETS and not past_comparator:
1433 return False
1434 if not past_comparator and (
1435 line.bracket_tracker.delimiters.get(id(leaf)) == COMPARATOR_PRIORITY
1436 ):
1437 past_comparator = True
1438 try:
1439 rhs = _first_right_hand_split(line)
1440 except CannotSplit:
1441 return False
1442 return is_line_short_enough(rhs.head, mode=mode)
1445@dont_increase_indentation
1446def delimiter_split(
1447 line: Line, features: Collection[Feature], mode: Mode
1448) -> Iterator[Line]:
1449 """Split according to delimiters of the highest priority.
1451 If the appropriate Features are given, the split will add trailing commas
1452 also in function signatures and calls that contain `*` and `**`.
1453 """
1454 if len(line.leaves) == 0:
1455 raise CannotSplit("Line empty") from None
1456 last_leaf = line.leaves[-1]
1458 bt = line.bracket_tracker
1459 try:
1460 delimiter_priority = bt.max_delimiter_priority(exclude={id(last_leaf)})
1461 except ValueError:
1462 raise CannotSplit("No delimiters found") from None
1464 if (
1465 delimiter_priority == DOT_PRIORITY
1466 and bt.delimiter_count_with_priority(delimiter_priority) == 1
1467 ):
1468 raise CannotSplit("Splitting a single attribute from its owner looks wrong")
1470 if (
1471 Preview.hug_comparator in mode
1472 and delimiter_priority == COMPARATOR_PRIORITY
1473 and bt.delimiter_count_with_priority(delimiter_priority) == 1
1474 and _can_defer_lone_comparator_to_rhs(line, mode)
1475 ):
1476 raise CannotSplit("Bracketed RHS will explode via right_hand_split")
1478 current_line = Line(
1479 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets
1480 )
1481 lowest_depth = sys.maxsize
1482 trailing_comma_safe = True
1484 def append_to_line(leaf: Leaf) -> Iterator[Line]:
1485 """Append `leaf` to current line or to new line if appending impossible."""
1486 nonlocal current_line
1487 try:
1488 current_line.append_safe(leaf, preformatted=True)
1489 except ValueError:
1490 yield current_line
1492 current_line = Line(
1493 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets
1494 )
1495 current_line.append(leaf)
1497 def append_comments(leaf: Leaf) -> Iterator[Line]:
1498 for comment_after in line.comments_after(leaf):
1499 yield from append_to_line(comment_after)
1501 last_non_comment_leaf = _get_last_non_comment_leaf(line)
1502 for leaf_idx, leaf in enumerate(line.leaves):
1503 yield from append_to_line(leaf)
1505 previous_priority = leaf_idx > 0 and bt.delimiters.get(
1506 id(line.leaves[leaf_idx - 1])
1507 )
1508 if (
1509 previous_priority != delimiter_priority
1510 or delimiter_priority in MIGRATE_COMMENT_DELIMITERS
1511 ):
1512 yield from append_comments(leaf)
1514 lowest_depth = min(lowest_depth, leaf.bracket_depth)
1515 if trailing_comma_safe and leaf.bracket_depth == lowest_depth:
1516 trailing_comma_safe = _can_add_trailing_comma(leaf, features)
1518 if last_leaf.type == STANDALONE_COMMENT and leaf_idx == last_non_comment_leaf:
1519 current_line = _safe_add_trailing_comma(
1520 trailing_comma_safe, delimiter_priority, current_line
1521 )
1523 leaf_priority = bt.delimiters.get(id(leaf))
1524 if leaf_priority == delimiter_priority:
1525 if (
1526 leaf_idx + 1 < len(line.leaves)
1527 and delimiter_priority not in MIGRATE_COMMENT_DELIMITERS
1528 ):
1529 yield from append_comments(line.leaves[leaf_idx + 1])
1531 yield current_line
1532 current_line = Line(
1533 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets
1534 )
1536 if current_line:
1537 current_line = _safe_add_trailing_comma(
1538 trailing_comma_safe, delimiter_priority, current_line
1539 )
1540 yield current_line
1543@dont_increase_indentation
1544def standalone_comment_split(
1545 line: Line, features: Collection[Feature], mode: Mode
1546) -> Iterator[Line]:
1547 """Split standalone comments from the rest of the line."""
1548 if not line.contains_standalone_comments():
1549 raise CannotSplit("Line does not have any standalone comments")
1551 current_line = Line(
1552 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets
1553 )
1555 def append_to_line(leaf: Leaf) -> Iterator[Line]:
1556 """Append `leaf` to current line or to new line if appending impossible."""
1557 nonlocal current_line
1558 try:
1559 current_line.append_safe(leaf, preformatted=True)
1560 except ValueError:
1561 yield current_line
1563 current_line = Line(
1564 line.mode, depth=line.depth, inside_brackets=line.inside_brackets
1565 )
1566 current_line.append(leaf)
1568 for leaf in line.leaves:
1569 yield from append_to_line(leaf)
1571 for comment_after in line.comments_after(leaf):
1572 yield from append_to_line(comment_after)
1574 if current_line:
1575 yield current_line
1578def _force_standalone_comment_split(line: Line) -> Iterator[Line]:
1579 """Last-resort split at every standalone-comment boundary."""
1580 current_line = Line(
1581 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets
1582 )
1583 for leaf in line.leaves:
1584 if current_line.leaves and (
1585 leaf.type == STANDALONE_COMMENT or current_line.is_comment
1586 ):
1587 yield current_line
1588 current_line = Line(
1589 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets
1590 )
1591 current_line.append(leaf, preformatted=True)
1592 if current_line:
1593 yield current_line
1596def _is_parenthesized_lambda_or_ternary(node: LN) -> bool:
1597 """Whether `node` is an atom wrapping a lambda or conditional expression,
1598 looking through any redundant nested parentheses.
1600 As a comprehension's iterable, such an expression must keep at least one pair
1601 of parentheses: without them the trailing `for`/`if` clauses would be parsed
1602 as part of the lambda body (or break the ternary), producing invalid code.
1603 """
1604 while (
1605 node.type == syms.atom
1606 and len(node.children) == 3
1607 and is_lpar_token(node.children[0])
1608 and is_rpar_token(node.children[-1])
1609 ):
1610 middle = node.children[1]
1611 if middle.type in {syms.test, syms.lambdef}:
1612 return True
1613 node = middle
1614 return False
1617def normalize_invisible_parens(
1618 node: Node, parens_after: set[str], *, mode: Mode, features: Collection[Feature]
1619) -> None:
1620 """Make existing optional parentheses invisible or create new ones.
1622 `parens_after` is a set of string leaf values immediately after which parens
1623 should be put.
1625 Standardizes on visible parentheses for single-element tuples, and keeps
1626 existing visible parentheses for other tuples and generator expressions.
1627 """
1628 for pc in list_comments(node.prefix, is_endmarker=False, mode=mode):
1629 if contains_fmt_directive(pc.value, FMT_OFF):
1630 # This `node` has a prefix with `# fmt: off`, don't mess with parens.
1631 return
1633 # The multiple context managers grammar has a different pattern, thus this is
1634 # separate from the for-loop below. This possibly wraps them in invisible parens,
1635 # and later will be removed in remove_with_parens when needed.
1636 if node.type == syms.with_stmt:
1637 _maybe_wrap_cms_in_parens(node, mode, features)
1639 check_lpar = False
1640 for index, child in enumerate(list(node.children)):
1641 # Fixes a bug where invisible parens are not properly stripped from
1642 # assignment statements that contain type annotations.
1643 if isinstance(child, Node) and child.type == syms.annassign:
1644 normalize_invisible_parens(
1645 child, parens_after=parens_after, mode=mode, features=features
1646 )
1648 # Fixes a bug where invisible parens are not properly wrapped around
1649 # case blocks.
1650 if isinstance(child, Node) and child.type == syms.case_block:
1651 normalize_invisible_parens(
1652 child, parens_after={"case"}, mode=mode, features=features
1653 )
1655 # Add parentheses around if guards in case blocks
1656 if isinstance(child, Node) and child.type == syms.guard:
1657 normalize_invisible_parens(
1658 child, parens_after={"if"}, mode=mode, features=features
1659 )
1661 # Add parentheses around long tuple unpacking in assignments.
1662 if (
1663 index == 0
1664 and isinstance(child, Node)
1665 and child.type == syms.testlist_star_expr
1666 ):
1667 check_lpar = True
1669 if (
1670 index == 0
1671 and isinstance(child, Node)
1672 and child.type == syms.atom
1673 and node.type == syms.expr_stmt
1674 and not _atom_has_magic_trailing_comma(child, mode)
1675 and not _is_atom_multiline(child)
1676 ):
1677 if maybe_make_parens_invisible_in_atom(
1678 child,
1679 parent=node,
1680 mode=mode,
1681 features=features,
1682 remove_brackets_around_comma=True,
1683 allow_star_expr=True,
1684 ):
1685 wrap_in_parentheses(node, child, visible=False)
1687 if check_lpar:
1688 if (
1689 child.type == syms.atom
1690 and node.type == syms.for_stmt
1691 and isinstance(child.prev_sibling, Leaf)
1692 and child.prev_sibling.type == token.NAME
1693 and child.prev_sibling.value == "for"
1694 ):
1695 if maybe_make_parens_invisible_in_atom(
1696 child,
1697 parent=node,
1698 mode=mode,
1699 features=features,
1700 remove_brackets_around_comma=True,
1701 ):
1702 wrap_in_parentheses(node, child, visible=False)
1703 elif isinstance(child, Node) and node.type == syms.with_stmt:
1704 remove_with_parens(child, node, mode=mode, features=features)
1705 elif (
1706 isinstance(child, Node)
1707 and node.type == syms.yield_expr
1708 and child.type == syms.yield_arg
1709 and Preview.parenthesize_tuple_in_yield in mode
1710 ):
1711 if (
1712 len(child.children) == 1
1713 and child.children[0].type != syms.atom
1714 and is_one_tuple(child.children[0])
1715 ):
1716 wrap_in_parentheses(node, child, visible=True)
1717 elif child.type == syms.atom:
1718 if "in" in parens_after and _is_parenthesized_lambda_or_ternary(child):
1719 # A lambda or conditional expression used as a comprehension's
1720 # iterable must keep at least one pair of parentheses, otherwise
1721 # the trailing `for`/`if` clauses get absorbed into it and the
1722 # code becomes invalid. Any extra nested pairs are redundant, so
1723 # collapse them while keeping exactly one visible pair.
1724 maybe_make_parens_invisible_in_atom(
1725 child, parent=node, mode=mode, features=features
1726 )
1727 opening = child.children[0]
1728 closing = child.children[-1]
1729 if is_lpar_token(opening) and is_rpar_token(closing):
1730 opening.value = "("
1731 closing.value = ")"
1732 elif maybe_make_parens_invisible_in_atom(
1733 child, parent=node, mode=mode, features=features
1734 ):
1735 wrap_in_parentheses(node, child, visible=False)
1736 elif is_one_tuple(child):
1737 wrap_in_parentheses(node, child, visible=True)
1738 elif node.type == syms.import_from:
1739 _normalize_import_from(node, child, index)
1740 break
1741 elif (
1742 index == 1
1743 and child.type == token.STAR
1744 and node.type == syms.except_clause
1745 ):
1746 # In except* (PEP 654), the star is actually part of
1747 # of the keyword. So we need to skip the insertion of
1748 # invisible parentheses to work more precisely.
1749 continue
1751 elif (
1752 isinstance(child, Leaf)
1753 and child.next_sibling is not None
1754 and child.next_sibling.type == token.COLON
1755 and child.value == "case"
1756 ):
1757 # A special patch for "case case:" scenario, the second occurrence
1758 # of case will be not parsed as a Python keyword.
1759 break
1761 elif isinstance(child, Node) and child.type == syms.guard:
1762 # Guard nodes handle their own inner wrapping. Wrapping the guard
1763 # itself can produce invalid output when the case pattern splits.
1764 pass
1766 elif not is_multiline_string(child):
1767 if (
1768 Preview.fix_if_guard_explosion_in_case_statement in mode
1769 and node.type == syms.guard
1770 ):
1771 mock_line = Line(mode=mode)
1772 for leaf in child.leaves():
1773 mock_line.append(leaf)
1774 # If it's a guard AND it's short, we DON'T wrap
1775 if not is_line_short_enough(mock_line, mode=mode):
1776 wrap_in_parentheses(node, child, visible=False)
1777 else:
1778 wrap_in_parentheses(node, child, visible=False)
1780 comma_check = child.type == token.COMMA
1782 check_lpar = isinstance(child, Leaf) and (
1783 child.value in parens_after or comma_check
1784 )
1787def _normalize_import_from(parent: Node, child: LN, index: int) -> None:
1788 # "import from" nodes store parentheses directly as part of
1789 # the statement
1790 if is_lpar_token(child):
1791 assert is_rpar_token(parent.children[-1])
1792 # make parentheses invisible
1793 child.value = ""
1794 parent.children[-1].value = ""
1795 elif child.type != token.STAR:
1796 # insert invisible parentheses
1797 parent.insert_child(index, Leaf(token.LPAR, ""))
1798 parent.append_child(Leaf(token.RPAR, ""))
1801def remove_await_parens(node: Node, mode: Mode, features: Collection[Feature]) -> None:
1802 if node.children[0].type == token.AWAIT and len(node.children) > 1:
1803 if (
1804 node.children[1].type == syms.atom
1805 and node.children[1].children[0].type == token.LPAR
1806 ):
1807 if maybe_make_parens_invisible_in_atom(
1808 node.children[1],
1809 parent=node,
1810 mode=mode,
1811 features=features,
1812 remove_brackets_around_comma=True,
1813 ):
1814 wrap_in_parentheses(node, node.children[1], visible=False)
1816 # Since await is an expression we shouldn't remove
1817 # brackets in cases where this would change
1818 # the AST due to operator precedence.
1819 # Therefore we only aim to remove brackets around
1820 # power nodes that aren't also await expressions themselves.
1821 # https://peps.python.org/pep-0492/#updated-operator-precedence-table
1822 # N.B. We've still removed any redundant nested brackets though :)
1823 opening_bracket = cast(Leaf, node.children[1].children[0])
1824 closing_bracket = cast(Leaf, node.children[1].children[-1])
1825 bracket_contents = node.children[1].children[1]
1826 if isinstance(bracket_contents, Node) and (
1827 bracket_contents.type != syms.power
1828 or bracket_contents.children[0].type == token.AWAIT
1829 or any(
1830 isinstance(child, Leaf) and child.type == token.DOUBLESTAR
1831 for child in bracket_contents.children
1832 )
1833 ):
1834 ensure_visible(opening_bracket)
1835 ensure_visible(closing_bracket)
1838def _maybe_wrap_cms_in_parens(
1839 node: Node, mode: Mode, features: Collection[Feature]
1840) -> None:
1841 """When enabled and safe, wrap the multiple context managers in invisible parens.
1843 It is only safe when `features` contain Feature.PARENTHESIZED_CONTEXT_MANAGERS.
1844 """
1845 if (
1846 Feature.PARENTHESIZED_CONTEXT_MANAGERS not in features
1847 or len(node.children) <= 2
1848 # If it's an atom, it's already wrapped in parens.
1849 or node.children[1].type == syms.atom
1850 ):
1851 return
1852 colon_index: int | None = None
1853 for i in range(2, len(node.children)):
1854 if node.children[i].type == token.COLON:
1855 colon_index = i
1856 break
1857 if colon_index is not None:
1858 lpar = Leaf(token.LPAR, "")
1859 rpar = Leaf(token.RPAR, "")
1860 context_managers = node.children[1:colon_index]
1861 for child in context_managers:
1862 child.remove()
1863 # After wrapping, the with_stmt will look like this:
1864 # with_stmt
1865 # NAME 'with'
1866 # atom
1867 # LPAR ''
1868 # testlist_gexp
1869 # ... <-- context_managers
1870 # /testlist_gexp
1871 # RPAR ''
1872 # /atom
1873 # COLON ':'
1874 new_child = Node(
1875 syms.atom, [lpar, Node(syms.testlist_gexp, context_managers), rpar]
1876 )
1877 node.insert_child(1, new_child)
1880def remove_with_parens(
1881 node: Node, parent: Node, mode: Mode, features: Collection[Feature]
1882) -> None:
1883 """Recursively hide optional parens in `with` statements."""
1884 # Removing all unnecessary parentheses in with statements in one pass is a tad
1885 # complex as different variations of bracketed statements result in pretty
1886 # different parse trees:
1887 #
1888 # with (open("file")) as f: # this is an asexpr_test
1889 # ...
1890 #
1891 # with (open("file") as f): # this is an atom containing an
1892 # ... # asexpr_test
1893 #
1894 # with (open("file")) as f, (open("file")) as f: # this is asexpr_test, COMMA,
1895 # ... # asexpr_test
1896 #
1897 # with (open("file") as f, open("file") as f): # an atom containing a
1898 # ... # testlist_gexp which then
1899 # # contains multiple asexpr_test(s)
1900 if node.type == syms.atom:
1901 if maybe_make_parens_invisible_in_atom(
1902 node,
1903 parent=parent,
1904 mode=mode,
1905 features=features,
1906 remove_brackets_around_comma=True,
1907 ):
1908 wrap_in_parentheses(parent, node, visible=False)
1909 if isinstance(node.children[1], Node):
1910 remove_with_parens(node.children[1], node, mode=mode, features=features)
1911 elif node.type == syms.testlist_gexp:
1912 for child in node.children:
1913 if isinstance(child, Node):
1914 remove_with_parens(child, node, mode=mode, features=features)
1915 elif node.type == syms.asexpr_test and not any(
1916 leaf.type == token.COLONEQUAL for leaf in node.leaves()
1917 ):
1918 if maybe_make_parens_invisible_in_atom(
1919 node.children[0],
1920 parent=node,
1921 mode=mode,
1922 features=features,
1923 remove_brackets_around_comma=True,
1924 ):
1925 wrap_in_parentheses(node, node.children[0], visible=False)
1928def _atom_has_magic_trailing_comma(node: LN, mode: Mode) -> bool:
1929 """Check if an atom node has a magic trailing comma.
1931 Returns True for single-element tuples with trailing commas like (a,),
1932 which should be preserved to maintain their tuple type.
1933 """
1934 if not mode.magic_trailing_comma:
1935 return False
1937 return is_one_tuple(node)
1940def _is_atom_multiline(node: LN) -> bool:
1941 """Check if an atom node is multiline (indicating intentional formatting)."""
1942 if not isinstance(node, Node) or len(node.children) < 3:
1943 return False
1945 # Check the middle child (between LPAR and RPAR) for newlines in its subtree
1946 # The first child's prefix contains blank lines/comments before the opening paren
1947 middle = node.children[1]
1948 for child in middle.pre_order():
1949 if isinstance(child, Leaf) and "\n" in child.prefix:
1950 return True
1952 return False
1955def maybe_make_parens_invisible_in_atom(
1956 node: LN,
1957 parent: LN,
1958 mode: Mode,
1959 features: Collection[Feature],
1960 remove_brackets_around_comma: bool = False,
1961 allow_star_expr: bool = False,
1962) -> bool:
1963 """If it's safe, make the parens in the atom `node` invisible, recursively.
1964 Additionally, remove repeated, adjacent invisible parens from the atom `node`
1965 as they are redundant.
1967 Returns whether the node should itself be wrapped in invisible parentheses.
1968 """
1969 if (
1970 node.type not in (syms.atom, syms.expr)
1971 or is_empty_tuple(node)
1972 or is_one_tuple(node)
1973 or (is_tuple(node) and parent.type == syms.asexpr_test)
1974 or (
1975 is_tuple(node)
1976 and parent.type == syms.with_stmt
1977 and has_sibling_with_type(node, token.COMMA)
1978 )
1979 or (is_yield(node) and parent.type != syms.expr_stmt)
1980 or (
1981 # This condition tries to prevent removing non-optional brackets
1982 # around a tuple, however, can be a bit overzealous so we provide
1983 # and option to skip this check for `for` and `with` statements.
1984 not remove_brackets_around_comma
1985 and max_delimiter_priority_in_atom(node) >= COMMA_PRIORITY
1986 # Remove parentheses around multiple exception types in except and
1987 # except* without as. See PEP 758 for details.
1988 and not (
1989 Feature.UNPARENTHESIZED_EXCEPT_TYPES in features
1990 # is a tuple
1991 and is_tuple(node)
1992 # has a parent node
1993 and node.parent is not None
1994 # parent is an except clause
1995 and node.parent.type == syms.except_clause
1996 # is not immediately followed by as clause
1997 and not (
1998 node.next_sibling is not None
1999 and is_name_token(node.next_sibling)
2000 and node.next_sibling.value == "as"
2001 )
2002 )
2003 )
2004 or is_tuple_containing_walrus(node)
2005 or (not allow_star_expr and is_tuple_containing_star(node))
2006 or is_generator(node)
2007 ):
2008 return False
2010 if is_walrus_assignment(node):
2011 if parent.type in [
2012 syms.annassign,
2013 syms.expr_stmt,
2014 syms.assert_stmt,
2015 syms.return_stmt,
2016 syms.yield_arg,
2017 syms.yield_expr,
2018 syms.except_clause,
2019 syms.funcdef,
2020 syms.with_stmt,
2021 syms.testlist_gexp,
2022 syms.tname,
2023 # these ones aren't useful to end users, but they do please fuzzers
2024 syms.for_stmt,
2025 syms.del_stmt,
2026 ]:
2027 return False
2029 first = node.children[0]
2030 last = node.children[-1]
2031 if is_lpar_token(first) and is_rpar_token(last):
2032 middle = node.children[1]
2033 # make parentheses invisible
2034 if (
2035 # If the prefix of `middle` includes a type comment with
2036 # ignore annotation, then we do not remove the parentheses
2037 not is_type_ignore_comment_string(middle.prefix.strip(), mode=mode)
2038 ):
2039 first.value = ""
2040 last.value = ""
2041 maybe_make_parens_invisible_in_atom(
2042 middle,
2043 parent=parent,
2044 mode=mode,
2045 features=features,
2046 remove_brackets_around_comma=remove_brackets_around_comma,
2047 )
2049 if is_atom_with_invisible_parens(middle):
2050 # Strip the invisible parens from `middle` by replacing
2051 # it with the child in-between the invisible parens
2052 middle.replace(middle.children[1])
2054 if middle.children[0].prefix.strip():
2055 # Preserve comments before first paren
2056 middle.children[1].prefix = (
2057 middle.children[0].prefix + middle.children[1].prefix
2058 )
2060 if middle.children[-1].prefix.strip():
2061 # Preserve comments before last paren
2062 last.prefix = middle.children[-1].prefix + last.prefix
2064 return False
2066 return True
2069def should_split_line(line: Line, opening_bracket: Leaf) -> bool:
2070 """Should `line` be immediately split with `delimiter_split()` after RHS?"""
2072 if not (opening_bracket.parent and opening_bracket.value in "[{("):
2073 return False
2075 # We're essentially checking if the body is delimited by commas and there's more
2076 # than one of them (we're excluding the trailing comma and if the delimiter priority
2077 # is still commas, that means there's more).
2078 exclude = set()
2079 trailing_comma = False
2080 try:
2081 last_leaf = line.leaves[-1]
2082 if last_leaf.type == token.COMMA:
2083 trailing_comma = True
2084 exclude.add(id(last_leaf))
2085 max_priority = line.bracket_tracker.max_delimiter_priority(exclude=exclude)
2086 except (IndexError, ValueError):
2087 return False
2089 return max_priority == COMMA_PRIORITY and (
2090 (line.mode.magic_trailing_comma and trailing_comma)
2091 # always explode imports
2092 or opening_bracket.parent.type in {syms.atom, syms.import_from}
2093 )
2096def generate_trailers_to_omit(line: Line, line_length: int) -> Iterator[set[LeafID]]:
2097 """Generate sets of closing bracket IDs that should be omitted in a RHS.
2099 Brackets can be omitted if the entire trailer up to and including
2100 a preceding closing bracket fits in one line.
2102 Yielded sets are cumulative (contain results of previous yields, too). First
2103 set is empty, unless the line should explode, in which case bracket pairs until
2104 the one that needs to explode are omitted.
2105 """
2107 omit: set[LeafID] = set()
2108 if not line.magic_trailing_comma:
2109 yield omit
2111 length = 4 * line.depth
2112 opening_bracket: Leaf | None = None
2113 closing_bracket: Leaf | None = None
2114 inner_brackets: set[LeafID] = set()
2115 for index, leaf, leaf_length in line.enumerate_with_length(is_reversed=True):
2116 length += leaf_length
2117 if length > line_length:
2118 break
2120 has_inline_comment = leaf_length > len(leaf.value) + len(leaf.prefix)
2121 if leaf.type == STANDALONE_COMMENT or has_inline_comment:
2122 break
2124 if opening_bracket:
2125 if leaf is opening_bracket:
2126 opening_bracket = None
2127 elif leaf.type in CLOSING_BRACKETS:
2128 prev = line.leaves[index - 1] if index > 0 else None
2129 if (
2130 prev
2131 and prev.type == token.COMMA
2132 and leaf.opening_bracket is not None
2133 and not is_one_sequence_between(
2134 leaf.opening_bracket, leaf, line.leaves
2135 )
2136 ):
2137 # Never omit bracket pairs with trailing commas.
2138 # We need to explode on those.
2139 break
2141 inner_brackets.add(id(leaf))
2142 elif leaf.type in CLOSING_BRACKETS:
2143 prev = line.leaves[index - 1] if index > 0 else None
2144 if prev and prev.type in OPENING_BRACKETS:
2145 # Empty brackets would fail a split so treat them as "inner"
2146 # brackets (e.g. only add them to the `omit` set if another
2147 # pair of brackets was good enough.
2148 inner_brackets.add(id(leaf))
2149 continue
2151 if closing_bracket:
2152 omit.add(id(closing_bracket))
2153 omit.update(inner_brackets)
2154 inner_brackets.clear()
2155 yield omit
2157 if (
2158 prev
2159 and prev.type == token.COMMA
2160 and leaf.opening_bracket is not None
2161 and not is_one_sequence_between(leaf.opening_bracket, leaf, line.leaves)
2162 ):
2163 # Never omit bracket pairs with trailing commas.
2164 # We need to explode on those.
2165 break
2167 if leaf.value:
2168 opening_bracket = leaf.opening_bracket
2169 closing_bracket = leaf
2172def _over_length_only_due_to_subscript_comment(line: Line, mode: Mode) -> bool:
2173 """Return True if `line` only exceeds `mode.line_length` because of an inline
2174 comment attached to a subscript opening bracket (`[`).
2176 This is the shape produced by the original of the issue #4733 reproducer:
2177 a comment inside the annotation's subscript brackets renders at the end of
2178 the head line after Black splits the statement, pushing it past the limit.
2179 Taking the FORCE_OPTIONAL_PARENTHESES "second opinion" in that case wraps
2180 the annotation in extra parens and migrates the comment outside the
2181 subscript, which then oscillates on the next formatter pass.
2182 """
2183 if not line.leaves:
2184 return False
2185 # The over-length must be caused entirely by a trailing comment.
2186 indent = " " * line.depth
2187 leaves_iter = iter(line.leaves)
2188 first = next(leaves_iter)
2189 text_without_comments = f"{first.prefix}{indent}{first.value}"
2190 text_without_comments += "".join(str(leaf) for leaf in leaves_iter)
2191 if str_width(text_without_comments) > mode.line_length:
2192 return False
2193 # And the comment must be attached to a subscript opening bracket.
2194 for leaf_id, comments in line.comments.items():
2195 if not comments:
2196 continue
2197 leaf = next((lf for lf in line.leaves if id(lf) == leaf_id), None)
2198 if leaf is None or leaf.type != token.LSQB:
2199 return False
2200 return True
2203def run_transformer(
2204 line: Line,
2205 transform: Transformer,
2206 mode: Mode,
2207 features: Collection[Feature],
2208 *,
2209 line_str: str = "",
2210) -> list[Line]:
2211 if not line_str:
2212 line_str = line_to_string(line)
2213 result: list[Line] = []
2214 for transformed_line in transform(line, features, mode):
2215 if str(transformed_line).strip("\n") == line_str:
2216 raise CannotTransform("Line transformer returned an unchanged result")
2218 result.extend(transform_line(transformed_line, mode=mode, features=features))
2220 features_set = set(features)
2221 if (
2222 Feature.FORCE_OPTIONAL_PARENTHESES in features_set
2223 or transform.__class__.__name__ != "rhs"
2224 or not line.bracket_tracker.invisible
2225 or any(bracket.value for bracket in line.bracket_tracker.invisible)
2226 or line.contains_multiline_strings()
2227 or result[0].contains_uncollapsable_type_comments()
2228 or result[0].contains_unsplittable_type_ignore()
2229 or is_line_short_enough(result[0], mode=mode)
2230 # result[0] only exceeds the length because of a comment attached to a
2231 # subscript opening bracket. Taking the FORCE_OPTIONAL_PARENTHESES
2232 # "second opinion" wraps the annotation in extra invisible parens and
2233 # migrates the comment outside the subscript, which then oscillates with
2234 # a deeper-bracket split on the next formatter pass (issue #4733).
2235 or _over_length_only_due_to_subscript_comment(result[0], mode)
2236 # If any leaves have no parents (which _can_ occur since
2237 # `transform(line)` potentially destroys the line's underlying node
2238 # structure), then we can't proceed. Doing so would cause the below
2239 # call to `append_leaves()` to fail.
2240 or any(leaf.parent is None for leaf in line.leaves)
2241 ):
2242 return result
2244 line_copy = line.clone()
2245 append_leaves(line_copy, line, line.leaves)
2246 features_fop = features_set | {Feature.FORCE_OPTIONAL_PARENTHESES}
2247 second_opinion = run_transformer(
2248 line_copy, transform, mode, features_fop, line_str=line_str
2249 )
2250 if all(is_line_short_enough(ln, mode=mode) for ln in second_opinion):
2251 result = second_opinion
2252 return result