1
2import ast
3import sys
4import dis
5from typing import cast, Any,Iterator
6import types
7
8
9
10def assert_(condition, message=""):
11 # type: (Any, str) -> None
12 """
13 Like an assert statement, but unaffected by -O
14 :param condition: value that is expected to be truthy
15 :type message: Any
16 """
17 if not condition:
18 raise AssertionError(str(message))
19
20
21if sys.version_info >= (3, 4):
22 # noinspection PyUnresolvedReferences
23 _get_instructions = dis.get_instructions
24 from dis import Instruction as _Instruction
25
26 class Instruction(_Instruction):
27 lineno = None # type: int
28else:
29 from collections import namedtuple
30
31 class Instruction(namedtuple('Instruction', 'offset argval opname starts_line')):
32 lineno = None # type: int
33
34 from dis import HAVE_ARGUMENT, EXTENDED_ARG, hasconst, opname, findlinestarts, hasname
35
36 # Based on dis.disassemble from 2.7
37 # Left as similar as possible for easy diff
38
39 def _get_instructions(co):
40 # type: (types.CodeType) -> Iterator[Instruction]
41 code = co.co_code
42 linestarts = dict(findlinestarts(co))
43 n = len(code)
44 i = 0
45 extended_arg = 0
46 while i < n:
47 offset = i
48 c = code[i]
49 op = ord(c)
50 lineno = linestarts.get(i)
51 argval = None
52 i = i + 1
53 if op >= HAVE_ARGUMENT:
54 oparg = ord(code[i]) + ord(code[i + 1]) * 256 + extended_arg
55 extended_arg = 0
56 i = i + 2
57 if op == EXTENDED_ARG:
58 extended_arg = oparg * 65536
59
60 if op in hasconst:
61 argval = co.co_consts[oparg]
62 elif op in hasname:
63 argval = co.co_names[oparg]
64 elif opname[op] == 'LOAD_FAST':
65 argval = co.co_varnames[oparg]
66 yield Instruction(offset, argval, opname[op], lineno)
67
68def get_instructions(co):
69 # type: (types.CodeType) -> Iterator[EnhancedInstruction]
70 lineno = co.co_firstlineno
71 for inst in _get_instructions(co):
72 inst = cast(EnhancedInstruction, inst)
73 lineno = inst.starts_line or lineno
74 assert_(lineno)
75 inst.lineno = lineno
76 yield inst
77
78
79# Type class used to expand out the definition of AST to include fields added by this library
80# It's not actually used for anything other than type checking though!
81class EnhancedAST(ast.AST):
82 parent = None # type: EnhancedAST
83
84# Type class used to expand out the definition of AST to include fields added by this library
85# It's not actually used for anything other than type checking though!
86class EnhancedInstruction(Instruction):
87 _copied = None # type: bool
88
89
90
91
92
93def mangled_name(node):
94 # type: (EnhancedAST) -> str
95 """
96
97 Parameters:
98 node: the node which should be mangled
99 name: the name of the node
100
101 Returns:
102 The mangled name of `node`
103 """
104
105 function_class_types=(ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)
106
107 if isinstance(node, ast.Attribute):
108 name = node.attr
109 elif isinstance(node, ast.Name):
110 name = node.id
111 elif isinstance(node, (ast.alias)):
112 name = node.asname or node.name.split(".")[0]
113 elif isinstance(node, function_class_types):
114 name = node.name
115 elif isinstance(node, ast.ExceptHandler):
116 assert node.name
117 name = node.name
118 elif sys.version_info >= (3,12) and isinstance(node,ast.TypeVar):
119 name=node.name
120 else:
121 raise TypeError("no node to mangle")
122
123 if name.startswith("__") and not name.endswith("__"):
124
125 parent,child=node.parent,node
126
127 while not (isinstance(parent,ast.ClassDef) and child not in parent.bases):
128 if not hasattr(parent,"parent"):
129 break # pragma: no mutate
130
131 parent,child=parent.parent,parent
132 else:
133 class_name=parent.name.lstrip("_")
134 if class_name!="" and child not in parent.decorator_list:
135 return "_" + class_name + name
136
137
138
139 return name