]> git.madduck.net Git - etc/vim.git/blob - src/black/nodes.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:

Remove regex dependency (GH-2663)
[etc/vim.git] / src / black / nodes.py
1 """
2 blib2to3 Node/Leaf transformation-related utility functions.
3 """
4
5 import sys
6 from typing import (
7     Collection,
8     Generic,
9     Iterator,
10     List,
11     Optional,
12     Set,
13     Tuple,
14     TypeVar,
15     Union,
16 )
17
18 if sys.version_info >= (3, 8):
19     from typing import Final
20 else:
21     from typing_extensions import Final
22
23 from mypy_extensions import mypyc_attr
24
25 # lib2to3 fork
26 from blib2to3.pytree import Node, Leaf, type_repr
27 from blib2to3 import pygram
28 from blib2to3.pgen2 import token
29
30 from black.cache import CACHE_DIR
31 from black.strings import has_triple_quotes
32
33
34 pygram.initialize(CACHE_DIR)
35 syms: Final = pygram.python_symbols
36
37
38 # types
39 T = TypeVar("T")
40 LN = Union[Leaf, Node]
41 LeafID = int
42 NodeType = int
43
44
45 WHITESPACE: Final = {token.DEDENT, token.INDENT, token.NEWLINE}
46 STATEMENT: Final = {
47     syms.if_stmt,
48     syms.while_stmt,
49     syms.for_stmt,
50     syms.try_stmt,
51     syms.except_clause,
52     syms.with_stmt,
53     syms.funcdef,
54     syms.classdef,
55     syms.match_stmt,
56     syms.case_block,
57 }
58 STANDALONE_COMMENT: Final = 153
59 token.tok_name[STANDALONE_COMMENT] = "STANDALONE_COMMENT"
60 LOGIC_OPERATORS: Final = {"and", "or"}
61 COMPARATORS: Final = {
62     token.LESS,
63     token.GREATER,
64     token.EQEQUAL,
65     token.NOTEQUAL,
66     token.LESSEQUAL,
67     token.GREATEREQUAL,
68 }
69 MATH_OPERATORS: Final = {
70     token.VBAR,
71     token.CIRCUMFLEX,
72     token.AMPER,
73     token.LEFTSHIFT,
74     token.RIGHTSHIFT,
75     token.PLUS,
76     token.MINUS,
77     token.STAR,
78     token.SLASH,
79     token.DOUBLESLASH,
80     token.PERCENT,
81     token.AT,
82     token.TILDE,
83     token.DOUBLESTAR,
84 }
85 STARS: Final = {token.STAR, token.DOUBLESTAR}
86 VARARGS_SPECIALS: Final = STARS | {token.SLASH}
87 VARARGS_PARENTS: Final = {
88     syms.arglist,
89     syms.argument,  # double star in arglist
90     syms.trailer,  # single argument to call
91     syms.typedargslist,
92     syms.varargslist,  # lambdas
93 }
94 UNPACKING_PARENTS: Final = {
95     syms.atom,  # single element of a list or set literal
96     syms.dictsetmaker,
97     syms.listmaker,
98     syms.testlist_gexp,
99     syms.testlist_star_expr,
100 }
101 TEST_DESCENDANTS: Final = {
102     syms.test,
103     syms.lambdef,
104     syms.or_test,
105     syms.and_test,
106     syms.not_test,
107     syms.comparison,
108     syms.star_expr,
109     syms.expr,
110     syms.xor_expr,
111     syms.and_expr,
112     syms.shift_expr,
113     syms.arith_expr,
114     syms.trailer,
115     syms.term,
116     syms.power,
117 }
118 ASSIGNMENTS: Final = {
119     "=",
120     "+=",
121     "-=",
122     "*=",
123     "@=",
124     "/=",
125     "%=",
126     "&=",
127     "|=",
128     "^=",
129     "<<=",
130     ">>=",
131     "**=",
132     "//=",
133 }
134
135 IMPLICIT_TUPLE: Final = {syms.testlist, syms.testlist_star_expr, syms.exprlist}
136 BRACKET: Final = {
137     token.LPAR: token.RPAR,
138     token.LSQB: token.RSQB,
139     token.LBRACE: token.RBRACE,
140 }
141 OPENING_BRACKETS: Final = set(BRACKET.keys())
142 CLOSING_BRACKETS: Final = set(BRACKET.values())
143 BRACKETS: Final = OPENING_BRACKETS | CLOSING_BRACKETS
144 ALWAYS_NO_SPACE: Final = CLOSING_BRACKETS | {token.COMMA, STANDALONE_COMMENT}
145
146 RARROW = 55
147
148
149 @mypyc_attr(allow_interpreted_subclasses=True)
150 class Visitor(Generic[T]):
151     """Basic lib2to3 visitor that yields things of type `T` on `visit()`."""
152
153     def visit(self, node: LN) -> Iterator[T]:
154         """Main method to visit `node` and its children.
155
156         It tries to find a `visit_*()` method for the given `node.type`, like
157         `visit_simple_stmt` for Node objects or `visit_INDENT` for Leaf objects.
158         If no dedicated `visit_*()` method is found, chooses `visit_default()`
159         instead.
160
161         Then yields objects of type `T` from the selected visitor.
162         """
163         if node.type < 256:
164             name = token.tok_name[node.type]
165         else:
166             name = str(type_repr(node.type))
167         # We explicitly branch on whether a visitor exists (instead of
168         # using self.visit_default as the default arg to getattr) in order
169         # to save needing to create a bound method object and so mypyc can
170         # generate a native call to visit_default.
171         visitf = getattr(self, f"visit_{name}", None)
172         if visitf:
173             yield from visitf(node)
174         else:
175             yield from self.visit_default(node)
176
177     def visit_default(self, node: LN) -> Iterator[T]:
178         """Default `visit_*()` implementation. Recurses to children of `node`."""
179         if isinstance(node, Node):
180             for child in node.children:
181                 yield from self.visit(child)
182
183
184 def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str:  # noqa: C901
185     """Return whitespace prefix if needed for the given `leaf`.
186
187     `complex_subscript` signals whether the given leaf is part of a subscription
188     which has non-trivial arguments, like arithmetic expressions or function calls.
189     """
190     NO: Final = ""
191     SPACE: Final = " "
192     DOUBLESPACE: Final = "  "
193     t = leaf.type
194     p = leaf.parent
195     v = leaf.value
196     if t in ALWAYS_NO_SPACE:
197         return NO
198
199     if t == token.COMMENT:
200         return DOUBLESPACE
201
202     assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}"
203     if t == token.COLON and p.type not in {
204         syms.subscript,
205         syms.subscriptlist,
206         syms.sliceop,
207     }:
208         return NO
209
210     prev = leaf.prev_sibling
211     if not prev:
212         prevp = preceding_leaf(p)
213         if not prevp or prevp.type in OPENING_BRACKETS:
214             return NO
215
216         if t == token.COLON:
217             if prevp.type == token.COLON:
218                 return NO
219
220             elif prevp.type != token.COMMA and not complex_subscript:
221                 return NO
222
223             return SPACE
224
225         if prevp.type == token.EQUAL:
226             if prevp.parent:
227                 if prevp.parent.type in {
228                     syms.arglist,
229                     syms.argument,
230                     syms.parameters,
231                     syms.varargslist,
232                 }:
233                     return NO
234
235                 elif prevp.parent.type == syms.typedargslist:
236                     # A bit hacky: if the equal sign has whitespace, it means we
237                     # previously found it's a typed argument.  So, we're using
238                     # that, too.
239                     return prevp.prefix
240
241         elif prevp.type in VARARGS_SPECIALS:
242             if is_vararg(prevp, within=VARARGS_PARENTS | UNPACKING_PARENTS):
243                 return NO
244
245         elif prevp.type == token.COLON:
246             if prevp.parent and prevp.parent.type in {syms.subscript, syms.sliceop}:
247                 return SPACE if complex_subscript else NO
248
249         elif (
250             prevp.parent
251             and prevp.parent.type == syms.factor
252             and prevp.type in MATH_OPERATORS
253         ):
254             return NO
255
256         elif (
257             prevp.type == token.RIGHTSHIFT
258             and prevp.parent
259             and prevp.parent.type == syms.shift_expr
260             and prevp.prev_sibling
261             and prevp.prev_sibling.type == token.NAME
262             and prevp.prev_sibling.value == "print"  # type: ignore
263         ):
264             # Python 2 print chevron
265             return NO
266         elif prevp.type == token.AT and p.parent and p.parent.type == syms.decorator:
267             # no space in decorators
268             return NO
269
270     elif prev.type in OPENING_BRACKETS:
271         return NO
272
273     if p.type in {syms.parameters, syms.arglist}:
274         # untyped function signatures or calls
275         if not prev or prev.type != token.COMMA:
276             return NO
277
278     elif p.type == syms.varargslist:
279         # lambdas
280         if prev and prev.type != token.COMMA:
281             return NO
282
283     elif p.type == syms.typedargslist:
284         # typed function signatures
285         if not prev:
286             return NO
287
288         if t == token.EQUAL:
289             if prev.type != syms.tname:
290                 return NO
291
292         elif prev.type == token.EQUAL:
293             # A bit hacky: if the equal sign has whitespace, it means we
294             # previously found it's a typed argument.  So, we're using that, too.
295             return prev.prefix
296
297         elif prev.type != token.COMMA:
298             return NO
299
300     elif p.type == syms.tname:
301         # type names
302         if not prev:
303             prevp = preceding_leaf(p)
304             if not prevp or prevp.type != token.COMMA:
305                 return NO
306
307     elif p.type == syms.trailer:
308         # attributes and calls
309         if t == token.LPAR or t == token.RPAR:
310             return NO
311
312         if not prev:
313             if t == token.DOT:
314                 prevp = preceding_leaf(p)
315                 if not prevp or prevp.type != token.NUMBER:
316                     return NO
317
318             elif t == token.LSQB:
319                 return NO
320
321         elif prev.type != token.COMMA:
322             return NO
323
324     elif p.type == syms.argument:
325         # single argument
326         if t == token.EQUAL:
327             return NO
328
329         if not prev:
330             prevp = preceding_leaf(p)
331             if not prevp or prevp.type == token.LPAR:
332                 return NO
333
334         elif prev.type in {token.EQUAL} | VARARGS_SPECIALS:
335             return NO
336
337     elif p.type == syms.decorator:
338         # decorators
339         return NO
340
341     elif p.type == syms.dotted_name:
342         if prev:
343             return NO
344
345         prevp = preceding_leaf(p)
346         if not prevp or prevp.type == token.AT or prevp.type == token.DOT:
347             return NO
348
349     elif p.type == syms.classdef:
350         if t == token.LPAR:
351             return NO
352
353         if prev and prev.type == token.LPAR:
354             return NO
355
356     elif p.type in {syms.subscript, syms.sliceop}:
357         # indexing
358         if not prev:
359             assert p.parent is not None, "subscripts are always parented"
360             if p.parent.type == syms.subscriptlist:
361                 return SPACE
362
363             return NO
364
365         elif not complex_subscript:
366             return NO
367
368     elif p.type == syms.atom:
369         if prev and t == token.DOT:
370             # dots, but not the first one.
371             return NO
372
373     elif p.type == syms.dictsetmaker:
374         # dict unpacking
375         if prev and prev.type == token.DOUBLESTAR:
376             return NO
377
378     elif p.type in {syms.factor, syms.star_expr}:
379         # unary ops
380         if not prev:
381             prevp = preceding_leaf(p)
382             if not prevp or prevp.type in OPENING_BRACKETS:
383                 return NO
384
385             prevp_parent = prevp.parent
386             assert prevp_parent is not None
387             if prevp.type == token.COLON and prevp_parent.type in {
388                 syms.subscript,
389                 syms.sliceop,
390             }:
391                 return NO
392
393             elif prevp.type == token.EQUAL and prevp_parent.type == syms.argument:
394                 return NO
395
396         elif t in {token.NAME, token.NUMBER, token.STRING}:
397             return NO
398
399     elif p.type == syms.import_from:
400         if t == token.DOT:
401             if prev and prev.type == token.DOT:
402                 return NO
403
404         elif t == token.NAME:
405             if v == "import":
406                 return SPACE
407
408             if prev and prev.type == token.DOT:
409                 return NO
410
411     elif p.type == syms.sliceop:
412         return NO
413
414     return SPACE
415
416
417 def preceding_leaf(node: Optional[LN]) -> Optional[Leaf]:
418     """Return the first leaf that precedes `node`, if any."""
419     while node:
420         res = node.prev_sibling
421         if res:
422             if isinstance(res, Leaf):
423                 return res
424
425             try:
426                 return list(res.leaves())[-1]
427
428             except IndexError:
429                 return None
430
431         node = node.parent
432     return None
433
434
435 def prev_siblings_are(node: Optional[LN], tokens: List[Optional[NodeType]]) -> bool:
436     """Return if the `node` and its previous siblings match types against the provided
437     list of tokens; the provided `node`has its type matched against the last element in
438     the list.  `None` can be used as the first element to declare that the start of the
439     list is anchored at the start of its parent's children."""
440     if not tokens:
441         return True
442     if tokens[-1] is None:
443         return node is None
444     if not node:
445         return False
446     if node.type != tokens[-1]:
447         return False
448     return prev_siblings_are(node.prev_sibling, tokens[:-1])
449
450
451 def last_two_except(leaves: List[Leaf], omit: Collection[LeafID]) -> Tuple[Leaf, Leaf]:
452     """Return (penultimate, last) leaves skipping brackets in `omit` and contents."""
453     stop_after: Optional[Leaf] = None
454     last: Optional[Leaf] = None
455     for leaf in reversed(leaves):
456         if stop_after:
457             if leaf is stop_after:
458                 stop_after = None
459             continue
460
461         if last:
462             return leaf, last
463
464         if id(leaf) in omit:
465             stop_after = leaf.opening_bracket
466         else:
467             last = leaf
468     else:
469         raise LookupError("Last two leaves were also skipped")
470
471
472 def parent_type(node: Optional[LN]) -> Optional[NodeType]:
473     """
474     Returns:
475         @node.parent.type, if @node is not None and has a parent.
476             OR
477         None, otherwise.
478     """
479     if node is None or node.parent is None:
480         return None
481
482     return node.parent.type
483
484
485 def child_towards(ancestor: Node, descendant: LN) -> Optional[LN]:
486     """Return the child of `ancestor` that contains `descendant`."""
487     node: Optional[LN] = descendant
488     while node and node.parent != ancestor:
489         node = node.parent
490     return node
491
492
493 def replace_child(old_child: LN, new_child: LN) -> None:
494     """
495     Side Effects:
496         * If @old_child.parent is set, replace @old_child with @new_child in
497         @old_child's underlying Node structure.
498             OR
499         * Otherwise, this function does nothing.
500     """
501     parent = old_child.parent
502     if not parent:
503         return
504
505     child_idx = old_child.remove()
506     if child_idx is not None:
507         parent.insert_child(child_idx, new_child)
508
509
510 def container_of(leaf: Leaf) -> LN:
511     """Return `leaf` or one of its ancestors that is the topmost container of it.
512
513     By "container" we mean a node where `leaf` is the very first child.
514     """
515     same_prefix = leaf.prefix
516     container: LN = leaf
517     while container:
518         parent = container.parent
519         if parent is None:
520             break
521
522         if parent.children[0].prefix != same_prefix:
523             break
524
525         if parent.type == syms.file_input:
526             break
527
528         if parent.prev_sibling is not None and parent.prev_sibling.type in BRACKETS:
529             break
530
531         container = parent
532     return container
533
534
535 def first_leaf_column(node: Node) -> Optional[int]:
536     """Returns the column of the first leaf child of a node."""
537     for child in node.children:
538         if isinstance(child, Leaf):
539             return child.column
540     return None
541
542
543 def first_child_is_arith(node: Node) -> bool:
544     """Whether first child is an arithmetic or a binary arithmetic expression"""
545     expr_types = {
546         syms.arith_expr,
547         syms.shift_expr,
548         syms.xor_expr,
549         syms.and_expr,
550     }
551     return bool(node.children and node.children[0].type in expr_types)
552
553
554 def is_docstring(leaf: Leaf) -> bool:
555     if prev_siblings_are(
556         leaf.parent, [None, token.NEWLINE, token.INDENT, syms.simple_stmt]
557     ):
558         return True
559
560     # Multiline docstring on the same line as the `def`.
561     if prev_siblings_are(leaf.parent, [syms.parameters, token.COLON, syms.simple_stmt]):
562         # `syms.parameters` is only used in funcdefs and async_funcdefs in the Python
563         # grammar. We're safe to return True without further checks.
564         return True
565
566     return False
567
568
569 def is_empty_tuple(node: LN) -> bool:
570     """Return True if `node` holds an empty tuple."""
571     return (
572         node.type == syms.atom
573         and len(node.children) == 2
574         and node.children[0].type == token.LPAR
575         and node.children[1].type == token.RPAR
576     )
577
578
579 def is_one_tuple(node: LN) -> bool:
580     """Return True if `node` holds a tuple with one element, with or without parens."""
581     if node.type == syms.atom:
582         gexp = unwrap_singleton_parenthesis(node)
583         if gexp is None or gexp.type != syms.testlist_gexp:
584             return False
585
586         return len(gexp.children) == 2 and gexp.children[1].type == token.COMMA
587
588     return (
589         node.type in IMPLICIT_TUPLE
590         and len(node.children) == 2
591         and node.children[1].type == token.COMMA
592     )
593
594
595 def is_one_tuple_between(opening: Leaf, closing: Leaf, leaves: List[Leaf]) -> bool:
596     """Return True if content between `opening` and `closing` looks like a one-tuple."""
597     if opening.type != token.LPAR and closing.type != token.RPAR:
598         return False
599
600     depth = closing.bracket_depth + 1
601     for _opening_index, leaf in enumerate(leaves):
602         if leaf is opening:
603             break
604
605     else:
606         raise LookupError("Opening paren not found in `leaves`")
607
608     commas = 0
609     _opening_index += 1
610     for leaf in leaves[_opening_index:]:
611         if leaf is closing:
612             break
613
614         bracket_depth = leaf.bracket_depth
615         if bracket_depth == depth and leaf.type == token.COMMA:
616             commas += 1
617             if leaf.parent and leaf.parent.type in {
618                 syms.arglist,
619                 syms.typedargslist,
620             }:
621                 commas += 1
622                 break
623
624     return commas < 2
625
626
627 def is_walrus_assignment(node: LN) -> bool:
628     """Return True iff `node` is of the shape ( test := test )"""
629     inner = unwrap_singleton_parenthesis(node)
630     return inner is not None and inner.type == syms.namedexpr_test
631
632
633 def is_simple_decorator_trailer(node: LN, last: bool = False) -> bool:
634     """Return True iff `node` is a trailer valid in a simple decorator"""
635     return node.type == syms.trailer and (
636         (
637             len(node.children) == 2
638             and node.children[0].type == token.DOT
639             and node.children[1].type == token.NAME
640         )
641         # last trailer can be an argument-less parentheses pair
642         or (
643             last
644             and len(node.children) == 2
645             and node.children[0].type == token.LPAR
646             and node.children[1].type == token.RPAR
647         )
648         # last trailer can be arguments
649         or (
650             last
651             and len(node.children) == 3
652             and node.children[0].type == token.LPAR
653             # and node.children[1].type == syms.argument
654             and node.children[2].type == token.RPAR
655         )
656     )
657
658
659 def is_simple_decorator_expression(node: LN) -> bool:
660     """Return True iff `node` could be a 'dotted name' decorator
661
662     This function takes the node of the 'namedexpr_test' of the new decorator
663     grammar and test if it would be valid under the old decorator grammar.
664
665     The old grammar was: decorator: @ dotted_name [arguments] NEWLINE
666     The new grammar is : decorator: @ namedexpr_test NEWLINE
667     """
668     if node.type == token.NAME:
669         return True
670     if node.type == syms.power:
671         if node.children:
672             return (
673                 node.children[0].type == token.NAME
674                 and all(map(is_simple_decorator_trailer, node.children[1:-1]))
675                 and (
676                     len(node.children) < 2
677                     or is_simple_decorator_trailer(node.children[-1], last=True)
678                 )
679             )
680     return False
681
682
683 def is_yield(node: LN) -> bool:
684     """Return True if `node` holds a `yield` or `yield from` expression."""
685     if node.type == syms.yield_expr:
686         return True
687
688     if node.type == token.NAME and node.value == "yield":  # type: ignore
689         return True
690
691     if node.type != syms.atom:
692         return False
693
694     if len(node.children) != 3:
695         return False
696
697     lpar, expr, rpar = node.children
698     if lpar.type == token.LPAR and rpar.type == token.RPAR:
699         return is_yield(expr)
700
701     return False
702
703
704 def is_vararg(leaf: Leaf, within: Set[NodeType]) -> bool:
705     """Return True if `leaf` is a star or double star in a vararg or kwarg.
706
707     If `within` includes VARARGS_PARENTS, this applies to function signatures.
708     If `within` includes UNPACKING_PARENTS, it applies to right hand-side
709     extended iterable unpacking (PEP 3132) and additional unpacking
710     generalizations (PEP 448).
711     """
712     if leaf.type not in VARARGS_SPECIALS or not leaf.parent:
713         return False
714
715     p = leaf.parent
716     if p.type == syms.star_expr:
717         # Star expressions are also used as assignment targets in extended
718         # iterable unpacking (PEP 3132).  See what its parent is instead.
719         if not p.parent:
720             return False
721
722         p = p.parent
723
724     return p.type in within
725
726
727 def is_multiline_string(leaf: Leaf) -> bool:
728     """Return True if `leaf` is a multiline string that actually spans many lines."""
729     return has_triple_quotes(leaf.value) and "\n" in leaf.value
730
731
732 def is_stub_suite(node: Node) -> bool:
733     """Return True if `node` is a suite with a stub body."""
734     if (
735         len(node.children) != 4
736         or node.children[0].type != token.NEWLINE
737         or node.children[1].type != token.INDENT
738         or node.children[3].type != token.DEDENT
739     ):
740         return False
741
742     return is_stub_body(node.children[2])
743
744
745 def is_stub_body(node: LN) -> bool:
746     """Return True if `node` is a simple statement containing an ellipsis."""
747     if not isinstance(node, Node) or node.type != syms.simple_stmt:
748         return False
749
750     if len(node.children) != 2:
751         return False
752
753     child = node.children[0]
754     return (
755         child.type == syms.atom
756         and len(child.children) == 3
757         and all(leaf == Leaf(token.DOT, ".") for leaf in child.children)
758     )
759
760
761 def is_atom_with_invisible_parens(node: LN) -> bool:
762     """Given a `LN`, determines whether it's an atom `node` with invisible
763     parens. Useful in dedupe-ing and normalizing parens.
764     """
765     if isinstance(node, Leaf) or node.type != syms.atom:
766         return False
767
768     first, last = node.children[0], node.children[-1]
769     return (
770         isinstance(first, Leaf)
771         and first.type == token.LPAR
772         and first.value == ""
773         and isinstance(last, Leaf)
774         and last.type == token.RPAR
775         and last.value == ""
776     )
777
778
779 def is_empty_par(leaf: Leaf) -> bool:
780     return is_empty_lpar(leaf) or is_empty_rpar(leaf)
781
782
783 def is_empty_lpar(leaf: Leaf) -> bool:
784     return leaf.type == token.LPAR and leaf.value == ""
785
786
787 def is_empty_rpar(leaf: Leaf) -> bool:
788     return leaf.type == token.RPAR and leaf.value == ""
789
790
791 def is_import(leaf: Leaf) -> bool:
792     """Return True if the given leaf starts an import statement."""
793     p = leaf.parent
794     t = leaf.type
795     v = leaf.value
796     return bool(
797         t == token.NAME
798         and (
799             (v == "import" and p and p.type == syms.import_name)
800             or (v == "from" and p and p.type == syms.import_from)
801         )
802     )
803
804
805 def is_type_comment(leaf: Leaf, suffix: str = "") -> bool:
806     """Return True if the given leaf is a special comment.
807     Only returns true for type comments for now."""
808     t = leaf.type
809     v = leaf.value
810     return t in {token.COMMENT, STANDALONE_COMMENT} and v.startswith("# type:" + suffix)
811
812
813 def wrap_in_parentheses(parent: Node, child: LN, *, visible: bool = True) -> None:
814     """Wrap `child` in parentheses.
815
816     This replaces `child` with an atom holding the parentheses and the old
817     child.  That requires moving the prefix.
818
819     If `visible` is False, the leaves will be valueless (and thus invisible).
820     """
821     lpar = Leaf(token.LPAR, "(" if visible else "")
822     rpar = Leaf(token.RPAR, ")" if visible else "")
823     prefix = child.prefix
824     child.prefix = ""
825     index = child.remove() or 0
826     new_child = Node(syms.atom, [lpar, child, rpar])
827     new_child.prefix = prefix
828     parent.insert_child(index, new_child)
829
830
831 def unwrap_singleton_parenthesis(node: LN) -> Optional[LN]:
832     """Returns `wrapped` if `node` is of the shape ( wrapped ).
833
834     Parenthesis can be optional. Returns None otherwise"""
835     if len(node.children) != 3:
836         return None
837
838     lpar, wrapped, rpar = node.children
839     if not (lpar.type == token.LPAR and rpar.type == token.RPAR):
840         return None
841
842     return wrapped
843
844
845 def ensure_visible(leaf: Leaf) -> None:
846     """Make sure parentheses are visible.
847
848     They could be invisible as part of some statements (see
849     :func:`normalize_invisible_parens` and :func:`visit_import_from`).
850     """
851     if leaf.type == token.LPAR:
852         leaf.value = "("
853     elif leaf.type == token.RPAR:
854         leaf.value = ")"