1"""
2Copy-parse of ast.dump, removing the `isinstance` checks. This is needed,
3because testing pegen requires generating a C extension module, which contains
4a copy of the symbols defined in Python-ast.c. Thus, the isinstance check would
5always fail. We rely on string comparison of the base classes instead.
6TODO: Remove the above-described hack.
7"""
8
9from typing import Any, Optional, Tuple
10
11
12def ast_dump(
13    node: Any,
14    annotate_fields: bool = True,
15    include_attributes: bool = False,
16    *,
17    indent: Optional[str] = None,
18) -> str:
19    def _format(node: Any, level: int = 0) -> Tuple[str, bool]:
20        if indent is not None:
21            level += 1
22            prefix = "\n" + indent * level
23            sep = ",\n" + indent * level
24        else:
25            prefix = ""
26            sep = ", "
27        if any(cls.__name__ == "AST" for cls in node.__class__.__mro__):
28            cls = type(node)
29            args = []
30            allsimple = True
31            keywords = annotate_fields
32            for name in node._fields:
33                try:
34                    value = getattr(node, name)
35                except AttributeError:
36                    keywords = True
37                    continue
38                if value is None and getattr(cls, name, ...) is None:
39                    keywords = True
40                    continue
41                value, simple = _format(value, level)
42                allsimple = allsimple and simple
43                if keywords:
44                    args.append("%s=%s" % (name, value))
45                else:
46                    args.append(value)
47            if include_attributes and node._attributes:
48                for name in node._attributes:
49                    try:
50                        value = getattr(node, name)
51                    except AttributeError:
52                        continue
53                    if value is None and getattr(cls, name, ...) is None:
54                        continue
55                    value, simple = _format(value, level)
56                    allsimple = allsimple and simple
57                    args.append("%s=%s" % (name, value))
58            if allsimple and len(args) <= 3:
59                return "%s(%s)" % (node.__class__.__name__, ", ".join(args)), not args
60            return "%s(%s%s)" % (node.__class__.__name__, prefix, sep.join(args)), False
61        elif isinstance(node, list):
62            if not node:
63                return "[]", True
64            return "[%s%s]" % (prefix, sep.join(_format(x, level)[0] for x in node)), False
65        return repr(node), True
66
67    if all(cls.__name__ != "AST" for cls in node.__class__.__mro__):
68        raise TypeError("expected AST, got %r" % node.__class__.__name__)
69    if indent is not None and not isinstance(indent, str):
70        indent = " " * indent
71    return _format(node)[0]
72