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

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