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

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