1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
4
5"""This module renders Astroid nodes as string"""
6
7from __future__ import annotations
8
9import warnings
10from collections.abc import Iterator
11from typing import TYPE_CHECKING
12
13from astroid import nodes
14
15if TYPE_CHECKING:
16 from astroid import objects
17
18# pylint: disable=unused-argument
19
20DOC_NEWLINE = "\0"
21
22
23# Visitor pattern require argument all the time and is not better with staticmethod
24# noinspection PyUnusedLocal,PyMethodMayBeStatic
25class AsStringVisitor:
26 """Visitor to render an Astroid node as a valid python code string"""
27
28 def __init__(self, indent: str = " "):
29 self.indent: str = indent
30
31 def __call__(self, node: nodes.NodeNG) -> str:
32 """Makes this visitor behave as a simple function"""
33 return node.accept(self).replace(DOC_NEWLINE, "\n")
34
35 def _docs_dedent(self, doc_node: nodes.Const | None) -> str:
36 """Stop newlines in docs being indented by self._stmt_list"""
37 if not doc_node:
38 return ""
39
40 return '\n{}"""{}"""'.format(
41 self.indent, doc_node.value.replace("\n", DOC_NEWLINE)
42 )
43
44 def _stmt_list(self, stmts: list, indent: bool = True) -> str:
45 """return a list of nodes to string"""
46 stmts_str: str = "\n".join(
47 nstr for nstr in [n.accept(self) for n in stmts] if nstr
48 )
49 if not indent:
50 return stmts_str
51
52 return self.indent + stmts_str.replace("\n", "\n" + self.indent)
53
54 def _precedence_parens(
55 self, node: nodes.NodeNG, child: nodes.NodeNG, is_left: bool = True
56 ) -> str:
57 """Wrap child in parens only if required to keep same semantics"""
58 if self._should_wrap(node, child, is_left):
59 return f"({child.accept(self)})"
60
61 return child.accept(self)
62
63 def _should_wrap(
64 self, node: nodes.NodeNG, child: nodes.NodeNG, is_left: bool
65 ) -> bool:
66 """Wrap child if:
67 - it has lower precedence
68 - same precedence with position opposite to associativity direction
69 """
70 node_precedence = node.op_precedence()
71 child_precedence = child.op_precedence()
72
73 if node_precedence > child_precedence:
74 # 3 * (4 + 5)
75 return True
76
77 if (
78 node_precedence == child_precedence
79 and is_left != node.op_left_associative()
80 ):
81 # 3 - (4 - 5)
82 # (2**3)**4
83 return True
84
85 return False
86
87 # visit_<node> methods ###########################################
88
89 def visit_await(self, node: nodes.Await) -> str:
90 return f"await {node.value.accept(self)}"
91
92 def visit_asyncwith(self, node: nodes.AsyncWith) -> str:
93 return f"async {self.visit_with(node)}"
94
95 def visit_asyncfor(self, node: nodes.AsyncFor) -> str:
96 return f"async {self.visit_for(node)}"
97
98 def visit_arguments(self, node: nodes.Arguments) -> str:
99 """return an nodes.Arguments node as string"""
100 return node.format_args()
101
102 def visit_assignattr(self, node: nodes.AssignAttr) -> str:
103 """return an nodes.AssignAttr node as string"""
104 return self.visit_attribute(node)
105
106 def visit_assert(self, node: nodes.Assert) -> str:
107 """return an nodes.Assert node as string"""
108 if node.fail:
109 return f"assert {node.test.accept(self)}, {node.fail.accept(self)}"
110 return f"assert {node.test.accept(self)}"
111
112 def visit_assignname(self, node: nodes.AssignName) -> str:
113 """return an nodes.AssignName node as string"""
114 return node.name
115
116 def visit_assign(self, node: nodes.Assign) -> str:
117 """return an nodes.Assign node as string"""
118 lhs = " = ".join(n.accept(self) for n in node.targets)
119 return f"{lhs} = {node.value.accept(self)}"
120
121 def visit_augassign(self, node: nodes.AugAssign) -> str:
122 """return an nodes.AugAssign node as string"""
123 return f"{node.target.accept(self)} {node.op} {node.value.accept(self)}"
124
125 def visit_annassign(self, node: nodes.AnnAssign) -> str:
126 """Return an nodes.AnnAssign node as string"""
127
128 target = node.target.accept(self)
129 annotation = node.annotation.accept(self)
130 if node.value is None:
131 return f"{target}: {annotation}"
132 return f"{target}: {annotation} = {node.value.accept(self)}"
133
134 def visit_binop(self, node: nodes.BinOp) -> str:
135 """return an nodes.BinOp node as string"""
136 left = self._precedence_parens(node, node.left)
137 right = self._precedence_parens(node, node.right, is_left=False)
138 if node.op == "**":
139 return f"{left}{node.op}{right}"
140
141 return f"{left} {node.op} {right}"
142
143 def visit_boolop(self, node: nodes.BoolOp) -> str:
144 """return an nodes.BoolOp node as string"""
145 values = [f"{self._precedence_parens(node, n)}" for n in node.values]
146 return (f" {node.op} ").join(values)
147
148 def visit_break(self, node: nodes.Break) -> str:
149 """return an nodes.Break node as string"""
150 return "break"
151
152 def visit_call(self, node: nodes.Call) -> str:
153 """return an nodes.Call node as string"""
154 expr_str = self._precedence_parens(node, node.func)
155 args = [arg.accept(self) for arg in node.args]
156 if node.keywords:
157 keywords = [kwarg.accept(self) for kwarg in node.keywords]
158 else:
159 keywords = []
160
161 args.extend(keywords)
162 return f"{expr_str}({', '.join(args)})"
163
164 def _handle_type_params(
165 self, type_params: list[nodes.TypeVar | nodes.ParamSpec | nodes.TypeVarTuple]
166 ) -> str:
167 return (
168 f"[{', '.join(tp.accept(self) for tp in type_params)}]"
169 if type_params
170 else ""
171 )
172
173 def visit_classdef(self, node: nodes.ClassDef) -> str:
174 """return an nodes.ClassDef node as string"""
175 decorate = node.decorators.accept(self) if node.decorators else ""
176 type_params = self._handle_type_params(node.type_params)
177 args = [n.accept(self) for n in node.bases]
178 if node._metaclass and not node.has_metaclass_hack():
179 args.append("metaclass=" + node._metaclass.accept(self))
180 args += [n.accept(self) for n in node.keywords]
181 args_str = f"({', '.join(args)})" if args else ""
182 docs = self._docs_dedent(node.doc_node)
183 body = self._stmt_list(node.body)
184 return (
185 f"\n\n{decorate}class {node.name}{type_params}{args_str}:{docs}\n{body}\n"
186 )
187
188 def visit_compare(self, node: nodes.Compare) -> str:
189 """return an nodes.Compare node as string"""
190 rhs_str = " ".join(
191 f"{op} {self._precedence_parens(node, expr, is_left=False)}"
192 for op, expr in node.ops
193 )
194 return f"{self._precedence_parens(node, node.left)} {rhs_str}"
195
196 def visit_comprehension(self, node: nodes.Comprehension) -> str:
197 """return an nodes.Comprehension node as string"""
198 ifs = "".join(f" if {n.accept(self)}" for n in node.ifs)
199 generated = f"for {node.target.accept(self)} in {node.iter.accept(self)}{ifs}"
200 return f"{'async ' if node.is_async else ''}{generated}"
201
202 def visit_const(self, node: nodes.Const) -> str:
203 """return an nodes.Const node as string"""
204 if node.value is Ellipsis:
205 return "..."
206 return repr(node.value)
207
208 def visit_continue(self, node: nodes.Continue) -> str:
209 """return an nodes.Continue node as string"""
210 return "continue"
211
212 def visit_delete(self, node: nodes.Delete) -> str:
213 """return an nodes.Delete node as string"""
214 return f"del {', '.join(child.accept(self) for child in node.targets)}"
215
216 def visit_delattr(self, node: nodes.DelAttr) -> str:
217 """return an nodes.DelAttr node as string"""
218 return self.visit_attribute(node)
219
220 def visit_delname(self, node: nodes.DelName) -> str:
221 """return an nodes.DelName node as string"""
222 return node.name
223
224 def visit_decorators(self, node: nodes.Decorators) -> str:
225 """return an nodes.Decorators node as string"""
226 joined = "\n@".join(item.accept(self) for item in node.nodes)
227 return f"@{joined}\n"
228
229 def visit_dict(self, node: nodes.Dict) -> str:
230 """return an nodes.Dict node as string"""
231 return f"{{{', '.join(self._visit_dict(node))}}}"
232
233 def _visit_dict(self, node: nodes.Dict) -> Iterator[str]:
234 for key, value in node.items:
235 key = key.accept(self)
236 value = value.accept(self)
237 if key == "**":
238 # It can only be a DictUnpack node.
239 yield key + value
240 else:
241 yield f"{key}: {value}"
242
243 def visit_dictunpack(self, node: nodes.DictUnpack) -> str:
244 return "**"
245
246 def visit_dictcomp(self, node: nodes.DictComp) -> str:
247 """return an nodes.DictComp node as string"""
248 key = node.key.accept(self)
249 value = node.value.accept(self)
250 generators = " ".join(n.accept(self) for n in node.generators)
251 if key == "**":
252 # PEP 798 dict-comprehension unpacking, e.g. ``{**d for d in dicts}``.
253 return f"{{{key}{value} {generators}}}"
254 return f"{{{key}: {value} {generators}}}"
255
256 def visit_expr(self, node: nodes.Expr) -> str:
257 """return an nodes.Expr node as string"""
258 return node.value.accept(self)
259
260 def visit_emptynode(self, node: nodes.EmptyNode) -> str:
261 """dummy method for visiting an EmptyNode"""
262 return ""
263
264 def visit_excepthandler(self, node: nodes.ExceptHandler) -> str:
265 n = "except"
266 if isinstance(getattr(node, "parent", None), nodes.TryStar):
267 n = "except*"
268 if node.type:
269 if node.name:
270 excs = f"{n} {node.type.accept(self)} as {node.name.accept(self)}"
271 else:
272 excs = f"{n} {node.type.accept(self)}"
273 else:
274 excs = f"{n}"
275 return f"{excs}:\n{self._stmt_list(node.body)}"
276
277 def visit_empty(self, node: nodes.EmptyNode) -> str:
278 """return an EmptyNode as string"""
279 return ""
280
281 def visit_for(self, node: nodes.For) -> str:
282 """return an nodes.For node as string"""
283 fors = f"for {node.target.accept(self)} in {node.iter.accept(self)}:\n{self._stmt_list(node.body)}"
284 if node.orelse:
285 fors = f"{fors}\nelse:\n{self._stmt_list(node.orelse)}"
286 return fors
287
288 def visit_importfrom(self, node: nodes.ImportFrom) -> str:
289 """return an nodes.ImportFrom node as string"""
290 lazy = "lazy " if node.is_lazy else ""
291 return "{}from {} import {}".format(
292 lazy, "." * (node.level or 0) + node.modname, _import_string(node.names)
293 )
294
295 def visit_joinedstr(self, node: nodes.JoinedStr) -> str:
296 string = "".join(
297 # Use repr on the string literal parts
298 # to get proper escapes, e.g. \n, \\, \"
299 # But strip the quotes off the ends
300 # (they will always be one character: ' or ")
301 (
302 repr(value.value)[1:-1]
303 # Literal braces must be doubled to escape them
304 .replace("{", "{{").replace("}", "}}")
305 # Each value in values is either a string literal (Const)
306 # or a FormattedValue
307 if type(value).__name__ == "Const"
308 else value.accept(self)
309 )
310 for value in node.values
311 )
312
313 # Try to find surrounding quotes that don't appear at all in the string.
314 # Because the formatted values inside {} can't contain backslash (\)
315 # using a triple quote is sometimes necessary
316 for quote in ("'", '"', '"""', "'''"):
317 if quote not in string:
318 break
319
320 return "f" + quote + string + quote
321
322 def visit_formattedvalue(self, node: nodes.FormattedValue) -> str:
323 result = node.value.accept(self)
324 if node.conversion and node.conversion >= 0:
325 # e.g. if node.conversion == 114: result += "!r"
326 result += "!" + chr(node.conversion)
327 if node.format_spec:
328 # The format spec is itself a JoinedString, i.e. an f-string
329 # We strip the f and quotes of the ends
330 result += ":" + node.format_spec.accept(self)[2:-1]
331 return f"{{{result}}}"
332
333 def handle_functiondef(self, node: nodes.FunctionDef, keyword: str) -> str:
334 """return a (possibly async) function definition node as string"""
335 decorate = node.decorators.accept(self) if node.decorators else ""
336 type_params = self._handle_type_params(node.type_params)
337 docs = self._docs_dedent(node.doc_node)
338 trailer = ":"
339 if node.returns:
340 return_annotation = " -> " + node.returns.as_string()
341 trailer = return_annotation + ":"
342 def_format = "\n%s%s %s%s(%s)%s%s\n%s"
343 return def_format % (
344 decorate,
345 keyword,
346 node.name,
347 type_params,
348 node.args.accept(self),
349 trailer,
350 docs,
351 self._stmt_list(node.body),
352 )
353
354 def visit_functiondef(self, node: nodes.FunctionDef) -> str:
355 """return an nodes.FunctionDef node as string"""
356 return self.handle_functiondef(node, "def")
357
358 def visit_asyncfunctiondef(self, node: nodes.AsyncFunctionDef) -> str:
359 """return an nodes.AsyncFunction node as string"""
360 return self.handle_functiondef(node, "async def")
361
362 def visit_generatorexp(self, node: nodes.GeneratorExp) -> str:
363 """return an nodes.GeneratorExp node as string"""
364 return "({} {})".format(
365 node.elt.accept(self), " ".join(n.accept(self) for n in node.generators)
366 )
367
368 def visit_attribute(
369 self, node: nodes.Attribute | nodes.AssignAttr | nodes.DelAttr
370 ) -> str:
371 """return an nodes.Attribute node as string"""
372 try:
373 left = self._precedence_parens(node, node.expr)
374 except RecursionError:
375 warnings.warn(
376 "Recursion limit exhausted; defaulting to adding parentheses.",
377 UserWarning,
378 stacklevel=2,
379 )
380 left = f"({node.expr.accept(self)})"
381 if left.isdigit():
382 left = f"({left})"
383 return f"{left}.{node.attrname}"
384
385 def visit_global(self, node: nodes.Global) -> str:
386 """return an nodes.Global node as string"""
387 return f"global {', '.join(node.names)}"
388
389 def visit_if(self, node: nodes.If) -> str:
390 """return an nodes.If node as string"""
391 ifs = [f"if {node.test.accept(self)}:\n{self._stmt_list(node.body)}"]
392 if node.has_elif_block():
393 ifs.append(f"el{self._stmt_list(node.orelse, indent=False)}")
394 elif node.orelse:
395 ifs.append(f"else:\n{self._stmt_list(node.orelse)}")
396 return "\n".join(ifs)
397
398 def visit_ifexp(self, node: nodes.IfExp) -> str:
399 """return an nodes.IfExp node as string"""
400 body = self._precedence_parens(node, node.body, is_left=True)
401 test = self._precedence_parens(node, node.test, is_left=True)
402 orelse = self._precedence_parens(node, node.orelse, is_left=False)
403 return f"{body} if {test} else {orelse}"
404
405 def visit_import(self, node: nodes.Import) -> str:
406 """return an nodes.Import node as string"""
407 lazy = "lazy " if node.is_lazy else ""
408 return f"{lazy}import {_import_string(node.names)}"
409
410 def visit_keyword(self, node: nodes.Keyword) -> str:
411 """return an nodes.Keyword node as string"""
412 if node.arg is None:
413 return f"**{node.value.accept(self)}"
414 return f"{node.arg}={node.value.accept(self)}"
415
416 def visit_lambda(self, node: nodes.Lambda) -> str:
417 """return an nodes.Lambda node as string"""
418 args = node.args.accept(self)
419 body = node.body.accept(self)
420 if args:
421 return f"lambda {args}: {body}"
422
423 return f"lambda: {body}"
424
425 def visit_list(self, node: nodes.List) -> str:
426 """return an nodes.List node as string"""
427 return f"[{', '.join(child.accept(self) for child in node.elts)}]"
428
429 def visit_listcomp(self, node: nodes.ListComp) -> str:
430 """return an nodes.ListComp node as string"""
431 return "[{} {}]".format(
432 node.elt.accept(self), " ".join(n.accept(self) for n in node.generators)
433 )
434
435 def visit_module(self, node: nodes.Module) -> str:
436 """return an nodes.Module node as string"""
437 docs = f'"""{node.doc_node.value}"""\n\n' if node.doc_node else ""
438 return docs + "\n".join(n.accept(self) for n in node.body) + "\n\n"
439
440 def visit_name(self, node: nodes.Name) -> str:
441 """return an nodes.Name node as string"""
442 return node.name
443
444 def visit_namedexpr(self, node: nodes.NamedExpr) -> str:
445 """Return an assignment expression node as string, always parenthesized."""
446 target = node.target.accept(self)
447 value = node.value.accept(self)
448 if isinstance(node.parent, nodes.Compare):
449 return f"{target} := {value}"
450 return f"({target} := {value})"
451
452 def visit_nonlocal(self, node: nodes.Nonlocal) -> str:
453 """return an nodes.Nonlocal node as string"""
454 return f"nonlocal {', '.join(node.names)}"
455
456 def visit_paramspec(self, node: nodes.ParamSpec) -> str:
457 """return an nodes.ParamSpec node as string"""
458 default_value_str = (
459 f" = {node.default_value.accept(self)}" if node.default_value else ""
460 )
461 return f"**{node.name.accept(self)}{default_value_str}"
462
463 def visit_pass(self, node: nodes.Pass) -> str:
464 """return an nodes.Pass node as string"""
465 return "pass"
466
467 def visit_partialfunction(self, node: objects.PartialFunction) -> str:
468 """Return an objects.PartialFunction as string."""
469 return self.visit_functiondef(node)
470
471 def visit_raise(self, node: nodes.Raise) -> str:
472 """return an nodes.Raise node as string"""
473 if node.exc:
474 if node.cause:
475 return f"raise {node.exc.accept(self)} from {node.cause.accept(self)}"
476 return f"raise {node.exc.accept(self)}"
477 return "raise"
478
479 def visit_return(self, node: nodes.Return) -> str:
480 """return an nodes.Return node as string"""
481 if node.is_tuple_return() and len(node.value.elts) > 1:
482 elts = [child.accept(self) for child in node.value.elts]
483 return f"return {', '.join(elts)}"
484
485 if node.value:
486 return f"return {node.value.accept(self)}"
487
488 return "return"
489
490 def visit_set(self, node: nodes.Set) -> str:
491 """return an nodes.Set node as string"""
492 return f"{{{', '.join(child.accept(self) for child in node.elts)}}}"
493
494 def visit_setcomp(self, node: nodes.SetComp) -> str:
495 """return an nodes.SetComp node as string"""
496 return "{{{} {}}}".format(
497 node.elt.accept(self), " ".join(n.accept(self) for n in node.generators)
498 )
499
500 def visit_slice(self, node: nodes.Slice) -> str:
501 """return an nodes.Slice node as string"""
502 lower = node.lower.accept(self) if node.lower else ""
503 upper = node.upper.accept(self) if node.upper else ""
504 step = node.step.accept(self) if node.step else ""
505 if step:
506 return f"{lower}:{upper}:{step}"
507 return f"{lower}:{upper}"
508
509 def visit_subscript(self, node: nodes.Subscript) -> str:
510 """return an nodes.Subscript node as string"""
511 idx = node.slice
512 if idx.__class__.__name__.lower() == "index":
513 idx = idx.value
514 idxstr = idx.accept(self)
515 if idx.__class__.__name__.lower() == "tuple" and idx.elts:
516 # Remove parenthesis in tuple and extended slice.
517 # a[(::1, 1:)] is not valid syntax.
518 idxstr = idxstr[1:-1]
519 return f"{self._precedence_parens(node, node.value)}[{idxstr}]"
520
521 def visit_try(self, node: nodes.Try) -> str:
522 """return an nodes.Try node as string"""
523 trys = [f"try:\n{self._stmt_list(node.body)}"]
524 for handler in node.handlers:
525 trys.append(handler.accept(self))
526 if node.orelse:
527 trys.append(f"else:\n{self._stmt_list(node.orelse)}")
528 if node.finalbody:
529 trys.append(f"finally:\n{self._stmt_list(node.finalbody)}")
530 return "\n".join(trys)
531
532 def visit_trystar(self, node: nodes.TryStar) -> str:
533 """return an nodes.TryStar node as string"""
534 trys = [f"try:\n{self._stmt_list(node.body)}"]
535 for handler in node.handlers:
536 trys.append(handler.accept(self))
537 if node.orelse:
538 trys.append(f"else:\n{self._stmt_list(node.orelse)}")
539 if node.finalbody:
540 trys.append(f"finally:\n{self._stmt_list(node.finalbody)}")
541 return "\n".join(trys)
542
543 def visit_tuple(self, node: nodes.Tuple) -> str:
544 """return an nodes.Tuple node as string"""
545 if len(node.elts) == 1:
546 return f"({node.elts[0].accept(self)}, )"
547 return f"({', '.join(child.accept(self) for child in node.elts)})"
548
549 def visit_typealias(self, node: nodes.TypeAlias) -> str:
550 """return an nodes.TypeAlias node as string"""
551 type_params = self._handle_type_params(node.type_params)
552 return f"type {node.name.accept(self)}{type_params} = {node.value.accept(self)}"
553
554 def visit_typevar(self, node: nodes.TypeVar) -> str:
555 """return an nodes.TypeVar node as string"""
556 bound_str = f": {node.bound.accept(self)}" if node.bound else ""
557 default_value_str = (
558 f" = {node.default_value.accept(self)}" if node.default_value else ""
559 )
560 return f"{node.name.accept(self)}{bound_str}{default_value_str}"
561
562 def visit_typevartuple(self, node: nodes.TypeVarTuple) -> str:
563 """return an nodes.TypeVarTuple node as string"""
564 default_value_str = (
565 f" = {node.default_value.accept(self)}" if node.default_value else ""
566 )
567 return f"*{node.name.accept(self)}{default_value_str}"
568
569 def visit_unaryop(self, node: nodes.UnaryOp) -> str:
570 """return an nodes.UnaryOp node as string"""
571 if node.op == "not":
572 operator = "not "
573 else:
574 operator = node.op
575 return f"{operator}{self._precedence_parens(node, node.operand)}"
576
577 def visit_while(self, node: nodes.While) -> str:
578 """return an nodes.While node as string"""
579 whiles = f"while {node.test.accept(self)}:\n{self._stmt_list(node.body)}"
580 if node.orelse:
581 whiles = f"{whiles}\nelse:\n{self._stmt_list(node.orelse)}"
582 return whiles
583
584 def visit_with(self, node: nodes.With) -> str: # 'with' without 'as' is possible
585 """return an nodes.With node as string"""
586 items = ", ".join(
587 f"{expr.accept(self)}" + ((v and f" as {v.accept(self)}") or "")
588 for expr, v in node.items
589 )
590 return f"with {items}:\n{self._stmt_list(node.body)}"
591
592 def visit_yield(self, node: nodes.Yield) -> str:
593 """yield an ast.Yield node as string"""
594 yi_val = (" " + node.value.accept(self)) if node.value else ""
595 expr = "yield" + yi_val
596 if node.parent.is_statement:
597 return expr
598
599 return f"({expr})"
600
601 def visit_yieldfrom(self, node: nodes.YieldFrom) -> str:
602 """Return an nodes.YieldFrom node as string."""
603 yi_val = (" " + node.value.accept(self)) if node.value else ""
604 expr = "yield from" + yi_val
605 if node.parent.is_statement:
606 return expr
607
608 return f"({expr})"
609
610 def visit_starred(self, node: nodes.Starred) -> str:
611 """return Starred node as string"""
612 return "*" + node.value.accept(self)
613
614 def visit_match(self, node: nodes.Match) -> str:
615 """Return an nodes.Match node as string."""
616 return f"match {node.subject.accept(self)}:\n{self._stmt_list(node.cases)}"
617
618 def visit_matchcase(self, node: nodes.MatchCase) -> str:
619 """Return an nodes.MatchCase node as string."""
620 guard_str = f" if {node.guard.accept(self)}" if node.guard else ""
621 return (
622 f"case {node.pattern.accept(self)}{guard_str}:\n"
623 f"{self._stmt_list(node.body)}"
624 )
625
626 def visit_matchvalue(self, node: nodes.MatchValue) -> str:
627 """Return an nodes.MatchValue node as string."""
628 return node.value.accept(self)
629
630 @staticmethod
631 def visit_matchsingleton(node: nodes.MatchSingleton) -> str:
632 """Return an nodes.MatchSingleton node as string."""
633 return str(node.value)
634
635 def visit_matchsequence(self, node: nodes.MatchSequence) -> str:
636 """Return an nodes.MatchSequence node as string."""
637 if node.patterns is None:
638 return "[]"
639 return f"[{', '.join(p.accept(self) for p in node.patterns)}]"
640
641 def visit_matchmapping(self, node: nodes.MatchMapping) -> str:
642 """Return an nodes..MatchMapping node as string."""
643 mapping_strings: list[str] = []
644 if node.keys and node.patterns:
645 mapping_strings.extend(
646 f"{key.accept(self)}: {p.accept(self)}"
647 for key, p in zip(node.keys, node.patterns)
648 )
649 if node.rest:
650 mapping_strings.append(f"**{node.rest.accept(self)}")
651 return f"{'{'}{', '.join(mapping_strings)}{'}'}"
652
653 def visit_matchclass(self, node: nodes.MatchClass) -> str:
654 """Return an nodes..MatchClass node as string."""
655 if node.cls is None:
656 raise AssertionError(f"{node} does not have a 'cls' node")
657 class_strings: list[str] = []
658 if node.patterns:
659 class_strings.extend(p.accept(self) for p in node.patterns)
660 if node.kwd_attrs and node.kwd_patterns:
661 for attr, pattern in zip(node.kwd_attrs, node.kwd_patterns):
662 class_strings.append(f"{attr}={pattern.accept(self)}")
663 return f"{node.cls.accept(self)}({', '.join(class_strings)})"
664
665 def visit_matchstar(self, node: nodes.MatchStar) -> str:
666 """Return an nodes..MatchStar node as string."""
667 return f"*{node.name.accept(self) if node.name else '_'}"
668
669 def visit_matchas(self, node: nodes.MatchAs) -> str:
670 """Return an nodes..MatchAs node as string."""
671 if isinstance(
672 node.parent, (nodes.MatchSequence, nodes.MatchMapping, nodes.MatchClass)
673 ):
674 return node.name.accept(self) if node.name else "_"
675 return (
676 f"{node.pattern.accept(self) if node.pattern else '_'}"
677 f"{f' as {node.name.accept(self)}' if node.name else ''}"
678 )
679
680 def visit_matchor(self, node: nodes.MatchOr) -> str:
681 """Return an nodes.MatchOr node as string."""
682 if node.patterns is None:
683 raise AssertionError(f"{node} does not have pattern nodes")
684 return " | ".join(p.accept(self) for p in node.patterns)
685
686 def visit_templatestr(self, node: nodes.TemplateStr) -> str:
687 """Return an nodes.TemplateStr node as string."""
688 string = ""
689 for value in node.values:
690 match value:
691 case nodes.Interpolation():
692 string += "{" + value.accept(self) + "}"
693 case _:
694 string += value.accept(self)[1:-1]
695 for quote in ("'", '"', '"""', "'''"):
696 if quote not in string:
697 break
698 return "t" + quote + string + quote
699
700 def visit_interpolation(self, node: nodes.Interpolation) -> str:
701 """Return an nodes.Interpolation node as string."""
702 result = f"{node.str}"
703 if node.conversion and node.conversion >= 0:
704 # e.g. if node.conversion == 114: result += "!r"
705 result += "!" + chr(node.conversion)
706 if node.format_spec:
707 # The format spec is itself a JoinedString, i.e. an f-string
708 # We strip the f and quotes of the ends
709 result += ":" + node.format_spec.accept(self)[2:-1]
710 return result
711
712 # These aren't for real AST nodes, but for inference objects.
713
714 def visit_frozenset(self, node: objects.FrozenSet) -> str:
715 return node.parent.accept(self)
716
717 def visit_super(self, node: objects.Super) -> str:
718 return node.parent.accept(self)
719
720 def visit_uninferable(self, node) -> str:
721 return str(node)
722
723 def visit_property(self, node: objects.Property) -> str:
724 return node.function.accept(self)
725
726 def visit_evaluatedobject(self, node: nodes.EvaluatedObject) -> str:
727 return node.original.accept(self)
728
729 def visit_unknown(self, node: nodes.Unknown) -> str:
730 return str(node)
731
732
733def _import_string(names: list[tuple[str, str | None]]) -> str:
734 """return a list of (name, asname) formatted as a string"""
735 _names = []
736 for name, asname in names:
737 if asname is not None:
738 _names.append(f"{name} as {asname}")
739 else:
740 _names.append(name)
741 return ", ".join(_names)
742
743
744# This sets the default indent to 4 spaces.
745to_code = AsStringVisitor(" ")