-
Notifications
You must be signed in to change notification settings - Fork 161
/
soa.py
151 lines (127 loc) · 5.75 KB
/
soa.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import rope.base.ast
import rope.base.oi.soi
import rope.base.pynames
from rope.base import pyobjects, evaluate, astutils, arguments
def analyze_module(pycore, pymodule, should_analyze, search_subscopes, followed_calls):
"""Analyze `pymodule` for static object inference
Analyzes scopes for collecting object information. The analysis
starts from inner scopes.
"""
_analyze_node(pycore, pymodule, should_analyze, search_subscopes, followed_calls)
def _analyze_node(pycore, pydefined, should_analyze, search_subscopes, followed_calls):
if search_subscopes(pydefined):
for scope in pydefined.get_scope().get_scopes():
_analyze_node(
pycore, scope.pyobject, should_analyze, search_subscopes, followed_calls
)
if should_analyze(pydefined):
new_followed_calls = max(0, followed_calls - 1)
return_true = lambda pydefined: True
return_false = lambda pydefined: False
def _follow(pyfunction):
_analyze_node(
pycore, pyfunction, return_true, return_false, new_followed_calls
)
if not followed_calls:
_follow = None
visitor = SOAVisitor(pycore, pydefined, _follow)
for child in rope.base.ast.iter_child_nodes(pydefined.get_ast()):
visitor.visit(child)
class SOAVisitor(rope.base.ast.RopeNodeVisitor):
def __init__(self, pycore, pydefined, follow_callback=None):
self.pycore = pycore
self.pymodule = pydefined.get_module()
self.scope = pydefined.get_scope()
self.follow = follow_callback
def _FunctionDef(self, node):
pass
def _ClassDef(self, node):
pass
def _Call(self, node):
for child in rope.base.ast.iter_child_nodes(node):
self.visit(child)
primary, pyname = evaluate.eval_node2(self.scope, node.func)
if pyname is None:
return
pyfunction = pyname.get_object()
if isinstance(pyfunction, pyobjects.AbstractFunction):
args = arguments.create_arguments(primary, pyfunction, node, self.scope)
elif isinstance(pyfunction, pyobjects.PyClass):
pyclass = pyfunction
if "__init__" in pyfunction:
pyfunction = pyfunction["__init__"].get_object()
pyname = rope.base.pynames.UnboundName(pyobjects.PyObject(pyclass))
args = self._args_with_self(primary, pyname, pyfunction, node)
elif "__call__" in pyfunction:
pyfunction = pyfunction["__call__"].get_object()
args = self._args_with_self(primary, pyname, pyfunction, node)
else:
return
self._call(pyfunction, args)
def _args_with_self(self, primary, self_pyname, pyfunction, node):
base_args = arguments.create_arguments(primary, pyfunction, node, self.scope)
return arguments.MixedArguments(self_pyname, base_args, self.scope)
def _call(self, pyfunction, args):
if isinstance(pyfunction, pyobjects.PyFunction):
if self.follow is not None:
before = self._parameter_objects(pyfunction)
self.pycore.object_info.function_called(
pyfunction, args.get_arguments(pyfunction.get_param_names())
)
pyfunction._set_parameter_pyobjects(None)
if self.follow is not None:
after = self._parameter_objects(pyfunction)
if after != before:
self.follow(pyfunction)
# XXX: Maybe we should not call every builtin function
if isinstance(pyfunction, rope.base.builtins.BuiltinFunction):
pyfunction.get_returned_object(args)
def _parameter_objects(self, pyfunction):
return [
pyfunction.get_parameter(i)
for i in range(len(pyfunction.get_param_names(False)))
]
def _AnnAssign(self, node):
for child in rope.base.ast.iter_child_nodes(node):
self.visit(child)
visitor = _SOAAssignVisitor()
nodes = []
visitor.visit(node.target)
nodes.extend(visitor.nodes)
self._evaluate_assign_value(node, nodes, type_hint=node.annotation)
def _Assign(self, node):
for child in rope.base.ast.iter_child_nodes(node):
self.visit(child)
visitor = _SOAAssignVisitor()
nodes = []
for child in node.targets:
visitor.visit(child)
nodes.extend(visitor.nodes)
self._evaluate_assign_value(node, nodes)
def _evaluate_assign_value(self, node, nodes, type_hint=False):
for subscript, levels in nodes:
instance = evaluate.eval_node(self.scope, subscript.value)
args_pynames = [evaluate.eval_node(self.scope, subscript.slice)]
value = rope.base.oi.soi._infer_assignment(
rope.base.pynames.AssignmentValue(
node.value, levels, type_hint=type_hint
),
self.pymodule,
)
args_pynames.append(rope.base.pynames.UnboundName(value))
if instance is not None and value is not None:
pyobject = instance.get_object()
if "__setitem__" in pyobject:
pyfunction = pyobject["__setitem__"].get_object()
args = arguments.ObjectArguments([instance] + args_pynames)
self._call(pyfunction, args)
# IDEA: handle `__setslice__`, too
class _SOAAssignVisitor(astutils._NodeNameCollector):
def __init__(self):
super().__init__()
self.nodes = []
def _added(self, node, levels):
if isinstance(node, rope.base.ast.Subscript) and isinstance(
node.slice, (rope.base.ast.Index, rope.base.ast.expr)
):
self.nodes.append((node, levels))