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