]> git.madduck.net Git - etc/vim.git/blob - src/black/lines.py

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Stability policy: permit exceptional changes for unformatted code (#3155)
[etc/vim.git] / src / black / lines.py
1 from dataclasses import dataclass, field
2 import itertools
3 import sys
4 from typing import (
5     Callable,
6     Dict,
7     Iterator,
8     List,
9     Optional,
10     Sequence,
11     Tuple,
12     TypeVar,
13     cast,
14 )
15
16 from blib2to3.pytree import Node, Leaf
17 from blib2to3.pgen2 import token
18
19 from black.brackets import BracketTracker, DOT_PRIORITY
20 from black.mode import Mode, Preview
21 from black.nodes import STANDALONE_COMMENT, TEST_DESCENDANTS
22 from black.nodes import BRACKETS, OPENING_BRACKETS, CLOSING_BRACKETS
23 from black.nodes import syms, whitespace, replace_child, child_towards
24 from black.nodes import is_multiline_string, is_import, is_type_comment
25 from black.nodes import is_one_sequence_between
26
27 # types
28 T = TypeVar("T")
29 Index = int
30 LeafID = int
31
32
33 @dataclass
34 class Line:
35     """Holds leaves and comments. Can be printed with `str(line)`."""
36
37     mode: Mode
38     depth: int = 0
39     leaves: List[Leaf] = field(default_factory=list)
40     # keys ordered like `leaves`
41     comments: Dict[LeafID, List[Leaf]] = field(default_factory=dict)
42     bracket_tracker: BracketTracker = field(default_factory=BracketTracker)
43     inside_brackets: bool = False
44     should_split_rhs: bool = False
45     magic_trailing_comma: Optional[Leaf] = None
46
47     def append(self, leaf: Leaf, preformatted: bool = False) -> None:
48         """Add a new `leaf` to the end of the line.
49
50         Unless `preformatted` is True, the `leaf` will receive a new consistent
51         whitespace prefix and metadata applied by :class:`BracketTracker`.
52         Trailing commas are maybe removed, unpacked for loop variables are
53         demoted from being delimiters.
54
55         Inline comments are put aside.
56         """
57         has_value = leaf.type in BRACKETS or bool(leaf.value.strip())
58         if not has_value:
59             return
60
61         if token.COLON == leaf.type and self.is_class_paren_empty:
62             del self.leaves[-2:]
63         if self.leaves and not preformatted:
64             # Note: at this point leaf.prefix should be empty except for
65             # imports, for which we only preserve newlines.
66             leaf.prefix += whitespace(
67                 leaf, complex_subscript=self.is_complex_subscript(leaf)
68             )
69         if self.inside_brackets or not preformatted:
70             self.bracket_tracker.mark(leaf)
71             if self.mode.magic_trailing_comma:
72                 if self.has_magic_trailing_comma(leaf):
73                     self.magic_trailing_comma = leaf
74             elif self.has_magic_trailing_comma(leaf, ensure_removable=True):
75                 self.remove_trailing_comma()
76         if not self.append_comment(leaf):
77             self.leaves.append(leaf)
78
79     def append_safe(self, leaf: Leaf, preformatted: bool = False) -> None:
80         """Like :func:`append()` but disallow invalid standalone comment structure.
81
82         Raises ValueError when any `leaf` is appended after a standalone comment
83         or when a standalone comment is not the first leaf on the line.
84         """
85         if self.bracket_tracker.depth == 0:
86             if self.is_comment:
87                 raise ValueError("cannot append to standalone comments")
88
89             if self.leaves and leaf.type == STANDALONE_COMMENT:
90                 raise ValueError(
91                     "cannot append standalone comments to a populated line"
92                 )
93
94         self.append(leaf, preformatted=preformatted)
95
96     @property
97     def is_comment(self) -> bool:
98         """Is this line a standalone comment?"""
99         return len(self.leaves) == 1 and self.leaves[0].type == STANDALONE_COMMENT
100
101     @property
102     def is_decorator(self) -> bool:
103         """Is this line a decorator?"""
104         return bool(self) and self.leaves[0].type == token.AT
105
106     @property
107     def is_import(self) -> bool:
108         """Is this an import line?"""
109         return bool(self) and is_import(self.leaves[0])
110
111     @property
112     def is_class(self) -> bool:
113         """Is this line a class definition?"""
114         return (
115             bool(self)
116             and self.leaves[0].type == token.NAME
117             and self.leaves[0].value == "class"
118         )
119
120     @property
121     def is_stub_class(self) -> bool:
122         """Is this line a class definition with a body consisting only of "..."?"""
123         return self.is_class and self.leaves[-3:] == [
124             Leaf(token.DOT, ".") for _ in range(3)
125         ]
126
127     @property
128     def is_def(self) -> bool:
129         """Is this a function definition? (Also returns True for async defs.)"""
130         try:
131             first_leaf = self.leaves[0]
132         except IndexError:
133             return False
134
135         try:
136             second_leaf: Optional[Leaf] = self.leaves[1]
137         except IndexError:
138             second_leaf = None
139         return (first_leaf.type == token.NAME and first_leaf.value == "def") or (
140             first_leaf.type == token.ASYNC
141             and second_leaf is not None
142             and second_leaf.type == token.NAME
143             and second_leaf.value == "def"
144         )
145
146     @property
147     def is_class_paren_empty(self) -> bool:
148         """Is this a class with no base classes but using parentheses?
149
150         Those are unnecessary and should be removed.
151         """
152         return (
153             bool(self)
154             and len(self.leaves) == 4
155             and self.is_class
156             and self.leaves[2].type == token.LPAR
157             and self.leaves[2].value == "("
158             and self.leaves[3].type == token.RPAR
159             and self.leaves[3].value == ")"
160         )
161
162     @property
163     def is_triple_quoted_string(self) -> bool:
164         """Is the line a triple quoted string?"""
165         return (
166             bool(self)
167             and self.leaves[0].type == token.STRING
168             and self.leaves[0].value.startswith(('"""', "'''"))
169         )
170
171     @property
172     def opens_block(self) -> bool:
173         """Does this line open a new level of indentation."""
174         if len(self.leaves) == 0:
175             return False
176         return self.leaves[-1].type == token.COLON
177
178     def contains_standalone_comments(self, depth_limit: int = sys.maxsize) -> bool:
179         """If so, needs to be split before emitting."""
180         for leaf in self.leaves:
181             if leaf.type == STANDALONE_COMMENT and leaf.bracket_depth <= depth_limit:
182                 return True
183
184         return False
185
186     def contains_uncollapsable_type_comments(self) -> bool:
187         ignored_ids = set()
188         try:
189             last_leaf = self.leaves[-1]
190             ignored_ids.add(id(last_leaf))
191             if last_leaf.type == token.COMMA or (
192                 last_leaf.type == token.RPAR and not last_leaf.value
193             ):
194                 # When trailing commas or optional parens are inserted by Black for
195                 # consistency, comments after the previous last element are not moved
196                 # (they don't have to, rendering will still be correct).  So we ignore
197                 # trailing commas and invisible.
198                 last_leaf = self.leaves[-2]
199                 ignored_ids.add(id(last_leaf))
200         except IndexError:
201             return False
202
203         # A type comment is uncollapsable if it is attached to a leaf
204         # that isn't at the end of the line (since that could cause it
205         # to get associated to a different argument) or if there are
206         # comments before it (since that could cause it to get hidden
207         # behind a comment.
208         comment_seen = False
209         for leaf_id, comments in self.comments.items():
210             for comment in comments:
211                 if is_type_comment(comment):
212                     if comment_seen or (
213                         not is_type_comment(comment, " ignore")
214                         and leaf_id not in ignored_ids
215                     ):
216                         return True
217
218                 comment_seen = True
219
220         return False
221
222     def contains_unsplittable_type_ignore(self) -> bool:
223         if not self.leaves:
224             return False
225
226         # If a 'type: ignore' is attached to the end of a line, we
227         # can't split the line, because we can't know which of the
228         # subexpressions the ignore was meant to apply to.
229         #
230         # We only want this to apply to actual physical lines from the
231         # original source, though: we don't want the presence of a
232         # 'type: ignore' at the end of a multiline expression to
233         # justify pushing it all onto one line. Thus we
234         # (unfortunately) need to check the actual source lines and
235         # only report an unsplittable 'type: ignore' if this line was
236         # one line in the original code.
237
238         # Grab the first and last line numbers, skipping generated leaves
239         first_line = next((leaf.lineno for leaf in self.leaves if leaf.lineno != 0), 0)
240         last_line = next(
241             (leaf.lineno for leaf in reversed(self.leaves) if leaf.lineno != 0), 0
242         )
243
244         if first_line == last_line:
245             # We look at the last two leaves since a comma or an
246             # invisible paren could have been added at the end of the
247             # line.
248             for node in self.leaves[-2:]:
249                 for comment in self.comments.get(id(node), []):
250                     if is_type_comment(comment, " ignore"):
251                         return True
252
253         return False
254
255     def contains_multiline_strings(self) -> bool:
256         return any(is_multiline_string(leaf) for leaf in self.leaves)
257
258     def has_magic_trailing_comma(
259         self, closing: Leaf, ensure_removable: bool = False
260     ) -> bool:
261         """Return True if we have a magic trailing comma, that is when:
262         - there's a trailing comma here
263         - it's not a one-tuple
264         - it's not a single-element subscript
265         Additionally, if ensure_removable:
266         - it's not from square bracket indexing
267         """
268         if not (
269             closing.type in CLOSING_BRACKETS
270             and self.leaves
271             and self.leaves[-1].type == token.COMMA
272         ):
273             return False
274
275         if closing.type == token.RBRACE:
276             return True
277
278         if closing.type == token.RSQB:
279             if (
280                 Preview.one_element_subscript in self.mode
281                 and closing.parent
282                 and closing.parent.type == syms.trailer
283                 and closing.opening_bracket
284                 and is_one_sequence_between(
285                     closing.opening_bracket,
286                     closing,
287                     self.leaves,
288                     brackets=(token.LSQB, token.RSQB),
289                 )
290             ):
291                 return False
292
293             if not ensure_removable:
294                 return True
295             comma = self.leaves[-1]
296             return bool(comma.parent and comma.parent.type == syms.listmaker)
297
298         if self.is_import:
299             return True
300
301         if closing.opening_bracket is not None and not is_one_sequence_between(
302             closing.opening_bracket, closing, self.leaves
303         ):
304             return True
305
306         return False
307
308     def append_comment(self, comment: Leaf) -> bool:
309         """Add an inline or standalone comment to the line."""
310         if (
311             comment.type == STANDALONE_COMMENT
312             and self.bracket_tracker.any_open_brackets()
313         ):
314             comment.prefix = ""
315             return False
316
317         if comment.type != token.COMMENT:
318             return False
319
320         if not self.leaves:
321             comment.type = STANDALONE_COMMENT
322             comment.prefix = ""
323             return False
324
325         last_leaf = self.leaves[-1]
326         if (
327             last_leaf.type == token.RPAR
328             and not last_leaf.value
329             and last_leaf.parent
330             and len(list(last_leaf.parent.leaves())) <= 3
331             and not is_type_comment(comment)
332         ):
333             # Comments on an optional parens wrapping a single leaf should belong to
334             # the wrapped node except if it's a type comment. Pinning the comment like
335             # this avoids unstable formatting caused by comment migration.
336             if len(self.leaves) < 2:
337                 comment.type = STANDALONE_COMMENT
338                 comment.prefix = ""
339                 return False
340
341             last_leaf = self.leaves[-2]
342         self.comments.setdefault(id(last_leaf), []).append(comment)
343         return True
344
345     def comments_after(self, leaf: Leaf) -> List[Leaf]:
346         """Generate comments that should appear directly after `leaf`."""
347         return self.comments.get(id(leaf), [])
348
349     def remove_trailing_comma(self) -> None:
350         """Remove the trailing comma and moves the comments attached to it."""
351         trailing_comma = self.leaves.pop()
352         trailing_comma_comments = self.comments.pop(id(trailing_comma), [])
353         self.comments.setdefault(id(self.leaves[-1]), []).extend(
354             trailing_comma_comments
355         )
356
357     def is_complex_subscript(self, leaf: Leaf) -> bool:
358         """Return True iff `leaf` is part of a slice with non-trivial exprs."""
359         open_lsqb = self.bracket_tracker.get_open_lsqb()
360         if open_lsqb is None:
361             return False
362
363         subscript_start = open_lsqb.next_sibling
364
365         if isinstance(subscript_start, Node):
366             if subscript_start.type == syms.listmaker:
367                 return False
368
369             if subscript_start.type == syms.subscriptlist:
370                 subscript_start = child_towards(subscript_start, leaf)
371         return subscript_start is not None and any(
372             n.type in TEST_DESCENDANTS for n in subscript_start.pre_order()
373         )
374
375     def enumerate_with_length(
376         self, reversed: bool = False
377     ) -> Iterator[Tuple[Index, Leaf, int]]:
378         """Return an enumeration of leaves with their length.
379
380         Stops prematurely on multiline strings and standalone comments.
381         """
382         op = cast(
383             Callable[[Sequence[Leaf]], Iterator[Tuple[Index, Leaf]]],
384             enumerate_reversed if reversed else enumerate,
385         )
386         for index, leaf in op(self.leaves):
387             length = len(leaf.prefix) + len(leaf.value)
388             if "\n" in leaf.value:
389                 return  # Multiline strings, we can't continue.
390
391             for comment in self.comments_after(leaf):
392                 length += len(comment.value)
393
394             yield index, leaf, length
395
396     def clone(self) -> "Line":
397         return Line(
398             mode=self.mode,
399             depth=self.depth,
400             inside_brackets=self.inside_brackets,
401             should_split_rhs=self.should_split_rhs,
402             magic_trailing_comma=self.magic_trailing_comma,
403         )
404
405     def __str__(self) -> str:
406         """Render the line."""
407         if not self:
408             return "\n"
409
410         indent = "    " * self.depth
411         leaves = iter(self.leaves)
412         first = next(leaves)
413         res = f"{first.prefix}{indent}{first.value}"
414         for leaf in leaves:
415             res += str(leaf)
416         for comment in itertools.chain.from_iterable(self.comments.values()):
417             res += str(comment)
418
419         return res + "\n"
420
421     def __bool__(self) -> bool:
422         """Return True if the line has leaves or comments."""
423         return bool(self.leaves or self.comments)
424
425
426 @dataclass
427 class EmptyLineTracker:
428     """Provides a stateful method that returns the number of potential extra
429     empty lines needed before and after the currently processed line.
430
431     Note: this tracker works on lines that haven't been split yet.  It assumes
432     the prefix of the first leaf consists of optional newlines.  Those newlines
433     are consumed by `maybe_empty_lines()` and included in the computation.
434     """
435
436     is_pyi: bool = False
437     previous_line: Optional[Line] = None
438     previous_after: int = 0
439     previous_defs: List[int] = field(default_factory=list)
440
441     def maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
442         """Return the number of extra empty lines before and after the `current_line`.
443
444         This is for separating `def`, `async def` and `class` with extra empty
445         lines (two on module-level).
446         """
447         before, after = self._maybe_empty_lines(current_line)
448         before = (
449             # Black should not insert empty lines at the beginning
450             # of the file
451             0
452             if self.previous_line is None
453             else before - self.previous_after
454         )
455         self.previous_after = after
456         self.previous_line = current_line
457         return before, after
458
459     def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
460         max_allowed = 1
461         if current_line.depth == 0:
462             max_allowed = 1 if self.is_pyi else 2
463         if current_line.leaves:
464             # Consume the first leaf's extra newlines.
465             first_leaf = current_line.leaves[0]
466             before = first_leaf.prefix.count("\n")
467             before = min(before, max_allowed)
468             first_leaf.prefix = ""
469         else:
470             before = 0
471         depth = current_line.depth
472         while self.previous_defs and self.previous_defs[-1] >= depth:
473             if self.is_pyi:
474                 assert self.previous_line is not None
475                 if depth and not current_line.is_def and self.previous_line.is_def:
476                     # Empty lines between attributes and methods should be preserved.
477                     before = min(1, before)
478                 elif depth:
479                     before = 0
480                 else:
481                     before = 1
482             else:
483                 if depth:
484                     before = 1
485                 elif (
486                     not depth
487                     and self.previous_defs[-1]
488                     and current_line.leaves[-1].type == token.COLON
489                     and (
490                         current_line.leaves[0].value
491                         not in ("with", "try", "for", "while", "if", "match")
492                     )
493                 ):
494                     # We shouldn't add two newlines between an indented function and
495                     # a dependent non-indented clause. This is to avoid issues with
496                     # conditional function definitions that are technically top-level
497                     # and therefore get two trailing newlines, but look weird and
498                     # inconsistent when they're followed by elif, else, etc. This is
499                     # worse because these functions only get *one* preceding newline
500                     # already.
501                     before = 1
502                 else:
503                     before = 2
504             self.previous_defs.pop()
505         if current_line.is_decorator or current_line.is_def or current_line.is_class:
506             return self._maybe_empty_lines_for_class_or_def(current_line, before)
507
508         if (
509             self.previous_line
510             and self.previous_line.is_import
511             and not current_line.is_import
512             and depth == self.previous_line.depth
513         ):
514             return (before or 1), 0
515
516         if (
517             self.previous_line
518             and self.previous_line.is_class
519             and current_line.is_triple_quoted_string
520         ):
521             return before, 1
522
523         if (
524             Preview.remove_block_trailing_newline in current_line.mode
525             and self.previous_line
526             and self.previous_line.opens_block
527         ):
528             return 0, 0
529         return before, 0
530
531     def _maybe_empty_lines_for_class_or_def(
532         self, current_line: Line, before: int
533     ) -> Tuple[int, int]:
534         if not current_line.is_decorator:
535             self.previous_defs.append(current_line.depth)
536         if self.previous_line is None:
537             # Don't insert empty lines before the first line in the file.
538             return 0, 0
539
540         if self.previous_line.is_decorator:
541             if self.is_pyi and current_line.is_stub_class:
542                 # Insert an empty line after a decorated stub class
543                 return 0, 1
544
545             return 0, 0
546
547         if self.previous_line.depth < current_line.depth and (
548             self.previous_line.is_class or self.previous_line.is_def
549         ):
550             return 0, 0
551
552         if (
553             self.previous_line.is_comment
554             and self.previous_line.depth == current_line.depth
555             and before == 0
556         ):
557             return 0, 0
558
559         if self.is_pyi:
560             if current_line.is_class or self.previous_line.is_class:
561                 if self.previous_line.depth < current_line.depth:
562                     newlines = 0
563                 elif self.previous_line.depth > current_line.depth:
564                     newlines = 1
565                 elif current_line.is_stub_class and self.previous_line.is_stub_class:
566                     # No blank line between classes with an empty body
567                     newlines = 0
568                 else:
569                     newlines = 1
570             elif (
571                 current_line.is_def or current_line.is_decorator
572             ) and not self.previous_line.is_def:
573                 if current_line.depth:
574                     # In classes empty lines between attributes and methods should
575                     # be preserved.
576                     newlines = min(1, before)
577                 else:
578                     # Blank line between a block of functions (maybe with preceding
579                     # decorators) and a block of non-functions
580                     newlines = 1
581             elif self.previous_line.depth > current_line.depth:
582                 newlines = 1
583             else:
584                 newlines = 0
585         else:
586             newlines = 1 if current_line.depth else 2
587         return newlines, 0
588
589
590 def enumerate_reversed(sequence: Sequence[T]) -> Iterator[Tuple[Index, T]]:
591     """Like `reversed(enumerate(sequence))` if that were possible."""
592     index = len(sequence) - 1
593     for element in reversed(sequence):
594         yield (index, element)
595         index -= 1
596
597
598 def append_leaves(
599     new_line: Line, old_line: Line, leaves: List[Leaf], preformatted: bool = False
600 ) -> None:
601     """
602     Append leaves (taken from @old_line) to @new_line, making sure to fix the
603     underlying Node structure where appropriate.
604
605     All of the leaves in @leaves are duplicated. The duplicates are then
606     appended to @new_line and used to replace their originals in the underlying
607     Node structure. Any comments attached to the old leaves are reattached to
608     the new leaves.
609
610     Pre-conditions:
611         set(@leaves) is a subset of set(@old_line.leaves).
612     """
613     for old_leaf in leaves:
614         new_leaf = Leaf(old_leaf.type, old_leaf.value)
615         replace_child(old_leaf, new_leaf)
616         new_line.append(new_leaf, preformatted=preformatted)
617
618         for comment_leaf in old_line.comments_after(old_leaf):
619             new_line.append(comment_leaf, preformatted=True)
620
621
622 def is_line_short_enough(line: Line, *, line_length: int, line_str: str = "") -> bool:
623     """Return True if `line` is no longer than `line_length`.
624
625     Uses the provided `line_str` rendering, if any, otherwise computes a new one.
626     """
627     if not line_str:
628         line_str = line_to_string(line)
629     return (
630         len(line_str) <= line_length
631         and "\n" not in line_str  # multiline strings
632         and not line.contains_standalone_comments()
633     )
634
635
636 def can_be_split(line: Line) -> bool:
637     """Return False if the line cannot be split *for sure*.
638
639     This is not an exhaustive search but a cheap heuristic that we can use to
640     avoid some unfortunate formattings (mostly around wrapping unsplittable code
641     in unnecessary parentheses).
642     """
643     leaves = line.leaves
644     if len(leaves) < 2:
645         return False
646
647     if leaves[0].type == token.STRING and leaves[1].type == token.DOT:
648         call_count = 0
649         dot_count = 0
650         next = leaves[-1]
651         for leaf in leaves[-2::-1]:
652             if leaf.type in OPENING_BRACKETS:
653                 if next.type not in CLOSING_BRACKETS:
654                     return False
655
656                 call_count += 1
657             elif leaf.type == token.DOT:
658                 dot_count += 1
659             elif leaf.type == token.NAME:
660                 if not (next.type == token.DOT or next.type in OPENING_BRACKETS):
661                     return False
662
663             elif leaf.type not in CLOSING_BRACKETS:
664                 return False
665
666             if dot_count > 1 and call_count > 1:
667                 return False
668
669     return True
670
671
672 def can_omit_invisible_parens(
673     line: Line,
674     line_length: int,
675 ) -> bool:
676     """Does `line` have a shape safe to reformat without optional parens around it?
677
678     Returns True for only a subset of potentially nice looking formattings but
679     the point is to not return false positives that end up producing lines that
680     are too long.
681     """
682     bt = line.bracket_tracker
683     if not bt.delimiters:
684         # Without delimiters the optional parentheses are useless.
685         return True
686
687     max_priority = bt.max_delimiter_priority()
688     if bt.delimiter_count_with_priority(max_priority) > 1:
689         # With more than one delimiter of a kind the optional parentheses read better.
690         return False
691
692     if max_priority == DOT_PRIORITY:
693         # A single stranded method call doesn't require optional parentheses.
694         return True
695
696     assert len(line.leaves) >= 2, "Stranded delimiter"
697
698     # With a single delimiter, omit if the expression starts or ends with
699     # a bracket.
700     first = line.leaves[0]
701     second = line.leaves[1]
702     if first.type in OPENING_BRACKETS and second.type not in CLOSING_BRACKETS:
703         if _can_omit_opening_paren(line, first=first, line_length=line_length):
704             return True
705
706         # Note: we are not returning False here because a line might have *both*
707         # a leading opening bracket and a trailing closing bracket.  If the
708         # opening bracket doesn't match our rule, maybe the closing will.
709
710     penultimate = line.leaves[-2]
711     last = line.leaves[-1]
712
713     if (
714         last.type == token.RPAR
715         or last.type == token.RBRACE
716         or (
717             # don't use indexing for omitting optional parentheses;
718             # it looks weird
719             last.type == token.RSQB
720             and last.parent
721             and last.parent.type != syms.trailer
722         )
723     ):
724         if penultimate.type in OPENING_BRACKETS:
725             # Empty brackets don't help.
726             return False
727
728         if is_multiline_string(first):
729             # Additional wrapping of a multiline string in this situation is
730             # unnecessary.
731             return True
732
733         if _can_omit_closing_paren(line, last=last, line_length=line_length):
734             return True
735
736     return False
737
738
739 def _can_omit_opening_paren(line: Line, *, first: Leaf, line_length: int) -> bool:
740     """See `can_omit_invisible_parens`."""
741     remainder = False
742     length = 4 * line.depth
743     _index = -1
744     for _index, leaf, leaf_length in line.enumerate_with_length():
745         if leaf.type in CLOSING_BRACKETS and leaf.opening_bracket is first:
746             remainder = True
747         if remainder:
748             length += leaf_length
749             if length > line_length:
750                 break
751
752             if leaf.type in OPENING_BRACKETS:
753                 # There are brackets we can further split on.
754                 remainder = False
755
756     else:
757         # checked the entire string and line length wasn't exceeded
758         if len(line.leaves) == _index + 1:
759             return True
760
761     return False
762
763
764 def _can_omit_closing_paren(line: Line, *, last: Leaf, line_length: int) -> bool:
765     """See `can_omit_invisible_parens`."""
766     length = 4 * line.depth
767     seen_other_brackets = False
768     for _index, leaf, leaf_length in line.enumerate_with_length():
769         length += leaf_length
770         if leaf is last.opening_bracket:
771             if seen_other_brackets or length <= line_length:
772                 return True
773
774         elif leaf.type in OPENING_BRACKETS:
775             # There are brackets we can further split on.
776             seen_other_brackets = True
777
778     return False
779
780
781 def line_to_string(line: Line) -> str:
782     """Returns the string representation of @line.
783
784     WARNING: This is known to be computationally expensive.
785     """
786     return str(line).strip("\n")