]> 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:

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