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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
|
# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
import collections
import six
from astroid import exceptions
from astroid.util import _singledispatch
from astroid.tree import treeabc
from astroid.tree import base as treebase
from astroid.interpreter import util
try:
from types import MappingProxyType
except ImportError:
from dictproxyhack import dictproxy as MappingProxyType
class LocalsDictNode(treebase.LookupMixIn,
treebase.NodeNG):
"""Provides locals handling common to Module, FunctionDef
and ClassDef nodes, including a dict like interface for direct access
to locals information
"""
# attributes below are set by the builder module or by raw factories
# dictionary of locals with name as key and node defining the local as
# value
@property
def locals(self):
return MappingProxyType(get_locals(self))
def frame(self):
"""return the first parent frame node (i.e. Module, FunctionDef or
ClassDef)
"""
return self
def _scope_lookup(self, node, name, offset=0):
"""XXX method for interfacing the scope lookup"""
try:
stmts = node._filter_stmts(self.locals[name], self, offset)
except KeyError:
stmts = ()
if stmts:
return self, stmts
if self.parent: # i.e. not Module
# nested scope: if parent scope is a function, that's fine
# else jump to the module
pscope = self.parent.scope()
if not pscope.is_function:
pscope = pscope.root()
return pscope.scope_lookup(node, name)
return builtin_lookup(name) # Module
def set_local(self, name, stmt):
raise Exception('Attempted locals mutation.')
# def set_local(self, name, stmt):
# """define <name> in locals (<stmt> is the node defining the name)
# if the node is a Module node (i.e. has globals), add the name to
# globals
# if the name is already defined, ignore it
# """
# #assert not stmt in self.locals.get(name, ()), (self, stmt)
# self.locals.setdefault(name, []).append(stmt)
__setitem__ = set_local
# def _append_node(self, child):
# """append a child, linking it in the tree"""
# self.body.append(child)
# child.parent = self
# def add_local_node(self, child_node, name=None):
# """append a child which should alter locals to the given node"""
# if name != '__class__':
# # add __class__ node as a child will cause infinite recursion later!
# self._append_node(child_node)
# self.set_local(name or child_node.name, child_node)
def __getitem__(self, item):
"""method from the `dict` interface returning the first node
associated with the given name in the locals dictionary
:type item: str
:param item: the name of the locally defined object
:raises KeyError: if the name is not defined
"""
return self.locals[item][0]
def __iter__(self):
"""method from the `dict` interface returning an iterator on
`self.keys()`
"""
return iter(self.locals)
def keys(self):
"""method from the `dict` interface returning a tuple containing
locally defined names
"""
return self.locals.keys()
def values(self):
"""method from the `dict` interface returning a tuple containing
locally defined nodes which are instance of `FunctionDef` or `ClassDef`
"""
return tuple(v[0] for v in self.locals.values())
def items(self):
"""method from the `dict` interface returning a list of tuple
containing each locally defined name with its associated node,
which is an instance of `FunctionDef` or `ClassDef`
"""
return tuple((k, v[0]) for k, v in self.locals.items())
def __contains__(self, name):
return name in self.locals
def builtin_lookup(name):
"""lookup a name into the builtin module
return the list of matching statements and the astroid for the builtin
module
"""
from astroid import MANAGER # TODO(cpopa) needs to be removed.
builtin_astroid = MANAGER.builtins()
if name == '__dict__':
return builtin_astroid, ()
stmts = builtin_astroid.locals.get(name, ())
# Use inference to find what AssignName nodes point to in builtins.
stmts = [next(s.infer()) if isinstance(s, treeabc.AssignName) else s
for s in stmts]
return builtin_astroid, stmts
@_singledispatch
def get_locals(node):
'''Return the local variables for an appropriate node.
For function nodes, this will be the local variables defined in
their scope, what would be returned by a locals() call in the
function body. For Modules, this will be all the global names
defined in the module, what would be returned by a locals() or
globals() call at the module level. For classes, this will be
class attributes defined in the class body, also what a locals()
call in the body would return.
This function starts by recursing over its argument's children to
avoid incorrectly adding a class's, function's, or module's name
to its own local variables.
Args:
node (LocalsDictNode): A node defining a scope to return locals for.
Returns:
A defaultdict(list) mapping names (strings) to lists of nodes.
Raises:
TypeError: When called on a node that doesn't represent a scope or a
non-node object.
'''
raise TypeError("This isn't an astroid node: %s" % type(node))
# pylint: disable=unused-variable; doesn't understand singledispatch
@get_locals.register(treeabc.NodeNG)
def not_scoped_node(node):
raise TypeError("This node doesn't have local variables: %s" % type(node))
# pylint: disable=unused-variable; doesn't understand singledispatch
@get_locals.register(LocalsDictNode)
def scoped_node(node):
locals_ = collections.defaultdict(list)
for n in node.get_children():
_get_locals(n, locals_)
return locals_
@_singledispatch
def _get_locals(node, locals_):
'''Return the local variables for a node.
This is the internal recursive generic function for gathering
nodes into a local variables mapping. The locals mapping is
passed down and mutated by each function.
Args:
node (NodeNG): The node to inspect for assignments to locals.
locals_ (defaultdict(list)): A mapping of (strings) to lists of nodes.
Raises:
TypeError: When called on a non-node object.
'''
raise TypeError('Non-astroid object in an astroid AST: %s' % type(node))
# pylint: disable=unused-variable; doesn't understand singledispatch
@_get_locals.register(treeabc.NodeNG)
def locals_generic(node, locals_):
'''Generic nodes don't create name bindings or scopes.'''
for n in node.get_children():
_get_locals(n, locals_)
# # pylint: disable=unused-variable; doesn't understand singledispatch
@_get_locals.register(LocalsDictNode)
def locals_new_scope(node, locals_):
'''These nodes start a new scope, so terminate recursion here.'''
# pylint: disable=unused-variable; doesn't understand singledispatch
@_get_locals.register(treeabc.AssignName)
@_get_locals.register(treeabc.DelName)
@_get_locals.register(treeabc.FunctionDef)
@_get_locals.register(treeabc.ClassDef)
@_get_locals.register(treeabc.Parameter)
def locals_name(node, locals_):
'''These nodes add a name to the local variables. AssignName and
DelName have no children while FunctionDef and ClassDef start a
new scope so shouldn't be recursed into.'''
locals_[node.name].append(node)
@_get_locals.register(treeabc.InterpreterObject)
def locals_interpreter_object(node, locals_):
'''InterpreterObjects add an object to the local variables under a specified
name.'''
if node.name:
locals_[node.name].append(node)
@_get_locals.register(treeabc.ReservedName)
def locals_reserved_name(node, locals_):
'''InterpreterObjects add an object to the local variables under a specified
name.'''
locals_[node.name].append(node.value)
# pylint: disable=unused-variable; doesn't understand singledispatch
@_get_locals.register(treeabc.Arguments)
def locals_arguments(node, locals_):
'''Other names assigned by functions have AssignName nodes that are
children of an Arguments node.'''
if node.vararg:
locals_[node.vararg].append(node)
if node.kwarg:
locals_[node.kwarg].append(node)
for n in node.get_children():
_get_locals(n, locals_)
# pylint: disable=unused-variable; doesn't understand singledispatch
@_get_locals.register(treeabc.Import)
def locals_import(node, locals_):
for name, asname in node.names:
name = asname or name
locals_[name.split('.')[0]].append(node)
# pylint: disable=unused-variable; doesn't understand singledispatch
@_get_locals.register(treeabc.ImportFrom)
def locals_import_from(node, locals_):
# Don't add future imports to locals.
if node.modname == '__future__':
return
# Sort the list for having the locals ordered by their first
# appearance.
def sort_locals(my_list):
my_list.sort(key=lambda node: node.fromlineno or 0)
for name, asname in node.names:
if name == '*':
try:
imported = util.do_import_module(node, node.modname)
except exceptions.AstroidBuildingError:
continue
for name in imported.public_names():
locals_[name].append(node)
sort_locals(locals_[name])
else:
locals_[asname or name].append(node)
sort_locals(locals_[asname or name])
|